diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02e6de413..dd6977679 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,6 +42,11 @@ repos: rev: 5.10.1 hooks: - id: isort + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.0.218' + hooks: + - id: ruff + args: ["--force-exclude"] - repo: https://github.com/codespell-project/codespell rev: v2.2.2 hooks: diff --git a/dvc/_debug.py b/dvc/_debug.py index 10cc63cc0..9cf68476e 100644 --- a/dvc/_debug.py +++ b/dvc/_debug.py @@ -12,7 +12,9 @@ def viztracer_profile(path: Union[Callable[[], str], str], depth: int = -1): try: import viztracer # pylint: disable=import-error except ImportError: - print("Failed to run profiler, viztracer is not installed") + print( # noqa: T201 + "Failed to run profiler, viztracer is not installed" + ) yield return @@ -34,7 +36,7 @@ def yappi_profile( try: import yappi # pylint: disable=import-error except ImportError: - print("Failed to run profiler, yappi is not installed") + print("Failed to run profiler, yappi is not installed") # noqa: T201 yield return @@ -60,7 +62,7 @@ def yappi_profile( st.save(out, type="callgrind") else: if ctx_id is not None: - print(f"\nThread {ctx_id}") + print(f"\nThread {ctx_id}") # noqa: T201 st.print_all() # pylint:disable=no-member if ctx_id is None: threads.print_all() # pylint:disable=no-member @@ -74,7 +76,9 @@ def instrument(html_output=False): try: from pyinstrument import Profiler # pylint: disable=import-error except ImportError: - print("Failed to run profiler, pyinstrument is not installed") + print( # noqa: T201 + "Failed to run profiler, pyinstrument is not installed" + ) yield return @@ -87,7 +91,7 @@ def instrument(html_output=False): if html_output: profiler.open_in_browser() return - print(profiler.output_text(unicode=True, color=True)) + print(profiler.output_text(unicode=True, color=True)) # noqa: T201 @contextmanager @@ -122,13 +126,15 @@ def debug(): def _sigshow(_, frame: Optional["FrameType"]) -> None: + import sys from shutil import get_terminal_size from traceback import format_stack - from dvc.ui import ui - lines = "\u2015" * get_terminal_size().columns - ui.error_write(lines, "\n", *format_stack(frame), lines, sep="") + file = sys.stderr + print( # noqa: T201 + lines, "\n", *format_stack(frame), lines, sep="", file=file + ) @contextmanager diff --git a/dvc/api/params.py b/dvc/api/params.py index 3218ea4ec..6cba9e98a 100644 --- a/dvc/api/params.py +++ b/dvc/api/params.py @@ -238,7 +238,7 @@ def params_show( if isinstance(stages, str): stages = [stages] - def _onerror_raise(result: Dict, exception: Exception, *args, **kwargs): + def _onerror_raise(_result: Dict, exception: Exception, *args, **kwargs): raise exception def _postprocess(params): diff --git a/dvc/cli/__init__.py b/dvc/cli/__init__.py index 2f700c086..4d96e3ebf 100644 --- a/dvc/cli/__init__.py +++ b/dvc/cli/__init__.py @@ -45,7 +45,6 @@ def _log_unknown_exceptions() -> None: if logger.isEnabledFor(logging.DEBUG): logger.debug("Version info for developers:\n%s", get_dvc_info()) logger.info(FOOTER) - return None def _log_exceptions(exc: Exception) -> Optional[int]: diff --git a/dvc/commands/cache.py b/dvc/commands/cache.py index 6d23fdfae..569b09212 100644 --- a/dvc/commands/cache.py +++ b/dvc/commands/cache.py @@ -42,7 +42,7 @@ def add_parser(subparsers, parent_parser): cache_subparsers = cache_parser.add_subparsers( dest="cmd", - help="Use `dvc cache CMD --help` for command-specific " "help.", + help="Use `dvc cache CMD --help` for command-specific help.", ) fix_subparsers(cache_subparsers) diff --git a/dvc/commands/check_ignore.py b/dvc/commands/check_ignore.py index fcfd9da57..bc21e4ef0 100644 --- a/dvc/commands/check_ignore.py +++ b/dvc/commands/check_ignore.py @@ -103,7 +103,7 @@ def add_parser(subparsers, parent_parser): "--all", action="store_true", default=False, - help="Include the target paths which don’t match any pattern " + help="Include the target paths which don't match any pattern " "in the `--details` list.", ) parser.add_argument( @@ -111,7 +111,7 @@ def add_parser(subparsers, parent_parser): "--non-matching", action="store_true", default=False, - help="Include the target paths which don’t match any pattern " + help="Include the target paths which don't match any pattern " "in the `--details` list.", ) parser.add_argument( diff --git a/dvc/commands/config.py b/dvc/commands/config.py index 054078c18..ddad68b06 100644 --- a/dvc/commands/config.py +++ b/dvc/commands/config.py @@ -37,13 +37,12 @@ class CmdConfig(CmdBaseNoRepo): self.config = Config(validate=False) def run(self): - if self.args.show_origin: - if any((self.args.value, self.args.unset)): - logger.error( - "--show-origin can't be used together with any of these " - "options: -u/--unset, value" - ) - return 1 + if self.args.show_origin and (self.args.value or self.args.unset): + logger.error( + "--show-origin can't be used together with any of these " + "options: -u/--unset, value" + ) + return 1 if self.args.list: return self._list() diff --git a/dvc/commands/daemon.py b/dvc/commands/daemon.py index 2a22de3d7..35d6e90e6 100644 --- a/dvc/commands/daemon.py +++ b/dvc/commands/daemon.py @@ -46,7 +46,7 @@ def add_parser(subparsers, parent_parser): daemon_subparsers = daemon_parser.add_subparsers( dest="cmd", - help="Use `dvc daemon CMD --help` for command-specific " "help.", + help="Use `dvc daemon CMD --help` for command-specific help.", ) fix_subparsers(daemon_subparsers) diff --git a/dvc/commands/data.py b/dvc/commands/data.py index 4fdb59d67..5f7279270 100644 --- a/dvc/commands/data.py +++ b/dvc/commands/data.py @@ -114,7 +114,9 @@ class CmdDataStatus(CmdBase): return 0 def run(self) -> int: - with log_durations(logger.trace, "in data_status"): # type: ignore + with log_durations( + logger.trace, "in data_status" # type: ignore[attr-defined] + ): status = self.repo.data_status( granular=self.args.granular, untracked_files=self.args.untracked_files, diff --git a/dvc/commands/data_sync.py b/dvc/commands/data_sync.py index 4b3937a67..a8fad6468 100644 --- a/dvc/commands/data_sync.py +++ b/dvc/commands/data_sync.py @@ -302,7 +302,7 @@ def add_parser(subparsers, _parent_parser): "--with-deps", action="store_true", default=False, - help="Fetch cache for all dependencies of the " "specified target.", + help="Fetch cache for all dependencies of the specified target.", ) fetch_parser.add_argument( "-R", diff --git a/dvc/commands/diff.py b/dvc/commands/diff.py index 9313f6dc6..1b2da7732 100644 --- a/dvc/commands/diff.py +++ b/dvc/commands/diff.py @@ -109,7 +109,7 @@ class CmdDiff(CmdBase): ui.write() if not sum(summary.values()): - return None + return states_summary = ", ".join( f"{summary[state]} {state}" diff --git a/dvc/commands/experiments/show.py b/dvc/commands/experiments/show.py index 03739cddb..efd0d5be3 100644 --- a/dvc/commands/experiments/show.py +++ b/dvc/commands/experiments/show.py @@ -359,7 +359,7 @@ def show_experiments( kwargs.get("iso"), ) if keep: - for col in td.keys(): + for col in td.keys(): # noqa: SIM118 if re.match(keep, col): td.protect(col) @@ -410,13 +410,15 @@ def show_experiments( cols_to_drop = set() if drop is not None: - cols_to_drop = {col for col in td.keys() if re.match(drop, col)} + cols_to_drop = { + col for col in td.keys() if re.match(drop, col) # noqa: SIM118 + } if pcp: cols_to_drop.add("Created") td.drop(*cols_to_drop) if pcp: - subset = {x for x in td.keys() if x != "Experiment"} + subset = {x for x in td.keys() if x != "Experiment"} # noqa: SIM118 td.dropna( "rows", how="all", @@ -494,7 +496,7 @@ class CmdExperimentsShow(CmdBase): else DEFAULT_PRECISION ) fill_value = "" if self.args.csv else FILL_VALUE - iso = True if self.args.csv else False + iso = self.args.csv show_experiments( all_experiments, diff --git a/dvc/commands/machine.py b/dvc/commands/machine.py index bc8a6e847..0a4c112ad 100644 --- a/dvc/commands/machine.py +++ b/dvc/commands/machine.py @@ -199,7 +199,7 @@ class CmdMachineDefault(CmdMachineConfig): if self.args.name is None and not self.args.unset: conf = self.config.read(self.args.level) try: - print(conf["core"]["machine"]) + ui.write(conf["core"]["machine"]) except KeyError: ui.write("No default machine set") return 1 @@ -324,7 +324,7 @@ def add_parser(subparsers, parent_parser): machine_subparsers = machine_parser.add_subparsers( dest="cmd", - help="Use `dvc machine CMD --help` for " "command-specific help.", + help="Use `dvc machine CMD --help` for command-specific help.", ) fix_subparsers(machine_subparsers) diff --git a/dvc/commands/plots.py b/dvc/commands/plots.py index 92b2d775e..ddca2147d 100644 --- a/dvc/commands/plots.py +++ b/dvc/commands/plots.py @@ -79,10 +79,8 @@ class CmdPlots(CmdBase): return {k: v for k, v in props.items() if v is not None} def _config_files(self): - config_files = None if self.args.from_config: - config_files = {self.args.from_config} - return config_files + return {self.args.from_config} def _html_template_path(self): html_template_path = self.args.html_template @@ -223,9 +221,8 @@ class CmdPlotsTemplates(CmdBase): return 0 raise InvalidArgumentError(f"Unexpected template: {target}.") - else: - for template in TEMPLATES: - ui.write(template.DEFAULT_NAME) + for template in TEMPLATES: + ui.write(template.DEFAULT_NAME) return 0 except DvcException: diff --git a/dvc/commands/queue/__init__.py b/dvc/commands/queue/__init__.py index e04e40172..73deb38b8 100644 --- a/dvc/commands/queue/__init__.py +++ b/dvc/commands/queue/__init__.py @@ -26,7 +26,7 @@ def add_parser(subparsers, parent_parser): queue_subparsers = queue_parser.add_subparsers( dest="cmd", - help="Use `dvc queue CMD --help` to display " "command-specific help.", + help="Use `dvc queue CMD --help` to display command-specific help.", ) fix_subparsers(queue_subparsers) diff --git a/dvc/commands/remote.py b/dvc/commands/remote.py index cd249cd5e..ebafa6beb 100644 --- a/dvc/commands/remote.py +++ b/dvc/commands/remote.py @@ -164,7 +164,7 @@ def add_parser(subparsers, parent_parser): remote_subparsers = remote_parser.add_subparsers( dest="cmd", - help="Use `dvc remote CMD --help` for " "command-specific help.", + help="Use `dvc remote CMD --help` for command-specific help.", ) fix_subparsers(remote_subparsers) diff --git a/dvc/commands/stage.py b/dvc/commands/stage.py index 334c68a8f..6c3262bc8 100644 --- a/dvc/commands/stage.py +++ b/dvc/commands/stage.py @@ -67,7 +67,7 @@ def prepare_stages_data( class CmdStageList(CmdBase): def _get_stages(self) -> Iterable["Stage"]: if self.args.all: - stages: List["Stage"] = self.repo.index.stages # type: ignore + stages: List["Stage"] = self.repo.index.stages logger.trace( # type: ignore[attr-defined] "%d no. of stages found", len(stages) ) diff --git a/dvc/compare.py b/dvc/compare.py index c90f99799..f1f5fd803 100644 --- a/dvc/compare.py +++ b/dvc/compare.py @@ -58,7 +58,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]): return self._columns[name] def items(self) -> ItemsView[str, Column]: - projection = {k: self.column(k) for k in self.keys()} + projection = {k: self.column(k) for k in self.keys()} # noqa: SIM118 return projection.items() def keys(self) -> List[str]: @@ -195,7 +195,8 @@ class TabularData(MutableSequence[Sequence["CellT"]]): self.add_column(key) row: List["CellT"] = [ - with_value(d.get(key), self._fill_value) for key in self.keys() + with_value(d.get(key), self._fill_value) + for key in self.keys() # noqa: SIM118 ] self.append(row) @@ -228,7 +229,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]): ) if how not in ["any", "all"]: raise ValueError( - f"Invalid 'how' value {how}." "Choose one of ['any', 'all']" + f"Invalid 'how' value {how}. Choose one of ['any', 'all']" ) match_line: Set = set() @@ -244,8 +245,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]): if axis == "rows": match_line.add(n_row) break - else: - match_line.add(self.keys()[n_col]) + match_line.add(self.keys()[n_col]) to_drop = match_line if how == "all": @@ -256,7 +256,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]): to_drop -= match_line if axis == "rows": - for name in self.keys(): + for name in self.keys(): # noqa: SIM118 self._columns[name] = Column( [ x @@ -309,7 +309,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]): else: unique_rows.append(tuple_row) - for name in self.keys(): + for name in self.keys(): # noqa: SIM118 self._columns[name] = Column( [ x diff --git a/dvc/dagascii.py b/dvc/dagascii.py index af99f23ad..dfe5c7721 100644 --- a/dvc/dagascii.py +++ b/dvc/dagascii.py @@ -59,8 +59,7 @@ class AsciiCanvas: def draw(self): """Draws ASCII canvas on the screen.""" lines = map("".join, self.canvas) - joined_lines = os.linesep.join(lines) - return joined_lines + return os.linesep.join(lines) def point(self, x, y, char): """Create a point on ASCII canvas. diff --git a/dvc/dependency/base.py b/dvc/dependency/base.py index 25f3b72fd..9588be3e1 100644 --- a/dvc/dependency/base.py +++ b/dvc/dependency/base.py @@ -25,11 +25,9 @@ class DependencyIsStageFileError(DvcException): class Dependency(Output): IS_DEPENDENCY = True - DoesNotExistError = DependencyDoesNotExistError # type: Type[DvcException] - IsNotFileOrDirError = ( - DependencyIsNotFileOrDirError - ) # type: Type[DvcException] - IsStageFileError = DependencyIsStageFileError # type: Type[DvcException] + DoesNotExistError: Type[DvcException] = DependencyDoesNotExistError + IsNotFileOrDirError: Type[DvcException] = DependencyIsNotFileOrDirError + IsStageFileError: Type[DvcException] = DependencyIsStageFileError def workspace_status(self) -> Dict[str, str]: if self.fs.version_aware: diff --git a/dvc/fs/__init__.py b/dvc/fs/__init__.py index 4141c2ed5..06b1a45ca 100644 --- a/dvc/fs/__init__.py +++ b/dvc/fs/__init__.py @@ -72,7 +72,9 @@ def get_fs_config(config, **kwargs): except KeyError: from dvc.config import RemoteNotFoundError - raise RemoteNotFoundError(f"remote '{name}' doesn't exist") + raise RemoteNotFoundError( # noqa: B904 + f"remote '{name}' doesn't exist" + ) else: remote_conf = kwargs return _resolve_remote_refs(config, remote_conf) diff --git a/dvc/fs/callbacks.py b/dvc/fs/callbacks.py index dc8a509eb..57fd5ee25 100644 --- a/dvc/fs/callbacks.py +++ b/dvc/fs/callbacks.py @@ -21,7 +21,7 @@ class RichCallback(Callback): value: int = 0, progress: "RichTransferProgress" = None, desc: str = None, - bytes: bool = False, # pylint: disable=redefined-builtin + bytes: bool = False, # noqa: A002, pylint: disable=redefined-builtin unit: str = None, disable: bool = False, transient: bool = True, diff --git a/dvc/fs/dvc.py b/dvc/fs/dvc.py index 712615177..bc1893384 100644 --- a/dvc/fs/dvc.py +++ b/dvc/fs/dvc.py @@ -156,14 +156,14 @@ class _DVCFileSystem(AbstractFileSystem): # pylint:disable=abstract-method def _get_key(self, path) -> Key: parts = self.repo.fs.path.relparts(path, self.repo.root_dir) - if parts == (".",): - parts = () + if parts == (os.curdir,): + return () return parts def _get_key_from_relative(self, path) -> Key: parts = self.path.relparts(path, self.root_marker) if parts and parts[0] == os.curdir: - parts = parts[1:] + return parts[1:] return parts def _from_key(self, parts: Key) -> str: diff --git a/dvc/ignore.py b/dvc/ignore.py index 2177768c2..a0e4caae5 100644 --- a/dvc/ignore.py +++ b/dvc/ignore.py @@ -24,11 +24,10 @@ class DvcIgnore: class DvcIgnorePatterns(DvcIgnore): def __init__(self, pattern_list, dirname, sep): - if pattern_list: - if isinstance(pattern_list[0], str): - pattern_list = [ - PatternInfo(pattern, "") for pattern in pattern_list - ] + if pattern_list and isinstance(pattern_list[0], str): + pattern_list = [ + PatternInfo(pattern, "") for pattern in pattern_list + ] self.sep = sep self.pattern_list = pattern_list @@ -84,7 +83,7 @@ class DvcIgnorePatterns(DvcIgnore): return False if os.name == "nt": - path = normalize_file(path) + return normalize_file(path) return path def matches(self, dirname, basename, is_dir=False, details: bool = False): @@ -195,8 +194,8 @@ class DvcIgnoreFilter: def _get_key(self, path): parts = self.fs.path.relparts(path, self.root_dir) - if parts == (".",): - parts = () + if parts == (os.curdir,): + return () return parts def _update_trie(self, dirname: str, trie: Trie) -> None: @@ -379,7 +378,7 @@ class DvcIgnoreFilter: return False def is_ignored_dir(self, path: str, ignore_subrepos: bool = True) -> bool: - "Only used in LocalFileSystem" + # only used in LocalFileSystem path = self.fs.path.abspath(path) if path == self.root_dir: return False @@ -387,7 +386,7 @@ class DvcIgnoreFilter: return self._is_ignored(path, True, ignore_subrepos=ignore_subrepos) def is_ignored_file(self, path: str, ignore_subrepos: bool = True) -> bool: - "Only used in LocalFileSystem" + # only used in LocalFileSystem path = self.fs.path.abspath(path) return self._is_ignored(path, False, ignore_subrepos=ignore_subrepos) diff --git a/dvc/lock.py b/dvc/lock.py index 17bbabc33..646188602 100644 --- a/dvc/lock.py +++ b/dvc/lock.py @@ -116,7 +116,7 @@ class Lock(LockBase): self._lock = zc.lockfile.LockFile(self._lockfile) except zc.lockfile.LockError: self._lock_failed = True - raise LockError(FAILED_TO_LOCK_MESSAGE) + raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904 def lock(self): retries = 6 @@ -185,14 +185,15 @@ class HardlinkLock(flufl.lock.Lock, LockBase): try: super().lock(timedelta(seconds=DEFAULT_TIMEOUT)) except flufl.lock.TimeOutError: - raise LockError(FAILED_TO_LOCK_MESSAGE) + raise LockError(FAILED_TO_LOCK_MESSAGE) # noqa: B904 def _set_claimfile(self): super()._set_claimfile() if self._tmp_dir is not None: # Under Windows file path length is limited so we hash it - filename = hashlib.md5(self._claimfile.encode()).hexdigest() + hasher = hashlib.md5(self._claimfile.encode()) # noqa: S324 + filename = hasher.hexdigest() self._claimfile = os.path.join(self._tmp_dir, filename + ".lock") diff --git a/dvc/machine/__init__.py b/dvc/machine/__init__.py index d93c3e7b9..1c2004d90 100644 --- a/dvc/machine/__init__.py +++ b/dvc/machine/__init__.py @@ -167,7 +167,9 @@ class MachineManager: except KeyError: from dvc.config import MachineNotFoundError - raise MachineNotFoundError(f"machine '{name}' doesn't exist") + raise MachineNotFoundError( # noqa: B904 + f"machine '{name}' doesn't exist" + ) else: conf = kwargs return conf @@ -179,7 +181,9 @@ class MachineManager: backend = self.CLOUD_BACKENDS[cloud] return self.backends[backend] except KeyError: - raise NoMachineError(f"Machine platform '{cloud}' unsupported") + raise NoMachineError( # noqa: B904 + f"Machine platform '{cloud}' unsupported" + ) def create(self, name: Optional[str]): """Create and start the specified machine instance.""" diff --git a/dvc/odbmgr.py b/dvc/odbmgr.py index 160cf4c54..219678b86 100644 --- a/dvc/odbmgr.py +++ b/dvc/odbmgr.py @@ -39,7 +39,7 @@ class ODBManager: from dvc.config_schema import LOCAL_COMMON settings = {"url": config["dir"]} - for opt in LOCAL_COMMON.keys(): + for opt in LOCAL_COMMON: if opt in config: settings[str(opt)] = config.get(opt) diff --git a/dvc/output.py b/dvc/output.py index 00f471778..a4b209901 100644 --- a/dvc/output.py +++ b/dvc/output.py @@ -267,10 +267,10 @@ class Output: }, ) - DoesNotExistError = OutputDoesNotExistError # type: Type[DvcException] - IsNotFileOrDirError = OutputIsNotFileOrDirError # type: Type[DvcException] - IsStageFileError = OutputIsStageFileError # type: Type[DvcException] - IsIgnoredError = OutputIsIgnoredError # type: Type[DvcException] + DoesNotExistError: Type[DvcException] = OutputDoesNotExistError + IsNotFileOrDirError: Type[DvcException] = OutputIsNotFileOrDirError + IsStageFileError: Type[DvcException] = OutputIsStageFileError + IsIgnoredError: Type[DvcException] = OutputIsIgnoredError def __init__( self, @@ -283,7 +283,7 @@ class Output: persist=False, checkpoint=False, desc=None, - type=None, # pylint: disable=redefined-builtin + type=None, # noqa: A002, pylint: disable=redefined-builtin labels=None, meta=None, remote=None, @@ -486,10 +486,9 @@ class Output: return self.hash_info.isdir def _is_path_dvcignore(self, path) -> bool: - if not self.IS_DEPENDENCY and self.dvcignore: - if self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False): - return True - return False + if self.IS_DEPENDENCY or not self.dvcignore: + return False + return self.dvcignore.is_ignored(self.fs, path, ignore_subrepos=False) @property def exists(self): @@ -675,11 +674,11 @@ class Output: try: return checkout(*args, **kwargs) except PromptError as exc: - raise ConfirmRemoveError(exc.path) + raise ConfirmRemoveError(exc.path) # noqa: B904 except LinkError as exc: - raise CacheLinkError([exc.path]) + raise CacheLinkError([exc.path]) # noqa: B904 except _CheckoutError as exc: - raise CheckoutError(exc.paths) + raise CheckoutError(exc.paths) # noqa: B904 def commit(self, filter_info=None): if not self.exists: @@ -762,12 +761,12 @@ class Output: if not self.use_cache: ret[self.PARAM_CACHE] = self.use_cache - if isinstance(self.metric, dict): - if ( - self.PARAM_METRIC_XPATH in self.metric - and not self.metric[self.PARAM_METRIC_XPATH] - ): - del self.metric[self.PARAM_METRIC_XPATH] + if ( + isinstance(self.metric, dict) + and self.PARAM_METRIC_XPATH in self.metric + and not self.metric[self.PARAM_METRIC_XPATH] + ): + del self.metric[self.PARAM_METRIC_XPATH] if self.metric: ret[self.PARAM_METRIC] = self.metric @@ -1020,7 +1019,7 @@ class Output: "Would you like to continue? Use '-f' to force." ) if not force and not prompt.confirm(msg.format(self.fs_path)): - raise CollectCacheError( + raise CollectCacheError( # noqa: B904 "unable to fully collect used cache" " without cache for directory '{}'".format(self) ) @@ -1032,7 +1031,7 @@ class Output: prefix = self.fs.path.parts( self.fs.path.relpath(filter_info, self.fs_path) ) - obj = obj.filter(prefix) + return obj.filter(prefix) return obj def get_used_objs( diff --git a/dvc/parsing/context.py b/dvc/parsing/context.py index 5f2967c22..0e8a39a07 100644 --- a/dvc/parsing/context.py +++ b/dvc/parsing/context.py @@ -414,19 +414,19 @@ class Context(CtxDict): self.imports[path].extend(cp) def check_loaded(self, path, item, keys): - if not keys and isinstance(self.imports[path], list): + imported = self.imports[path] + if not keys and isinstance(imported, list): raise VarsAlreadyLoaded( f"cannot load '{item}' as it's partially loaded already" ) - elif keys and self.imports[path] is None: + if keys and imported is None: raise VarsAlreadyLoaded( f"cannot partially load '{item}' as it's already loaded." ) - elif isinstance(self.imports[path], list): - if not set(keys).isdisjoint(set(self.imports[path])): - raise VarsAlreadyLoaded( - f"cannot load '{item}' as it's partially loaded already" - ) + if isinstance(imported, list) and set(keys) & set(imported): + raise VarsAlreadyLoaded( + f"cannot load '{item}' as it's partially loaded already" + ) def load_from_vars( self, @@ -482,7 +482,7 @@ class Context(CtxDict): try: yield finally: - for key in new.keys(): + for key in new: self._reserved_keys.pop(key) @contextmanager diff --git a/dvc/parsing/interpolate.py b/dvc/parsing/interpolate.py index db53c46bb..93e44be4d 100644 --- a/dvc/parsing/interpolate.py +++ b/dvc/parsing/interpolate.py @@ -186,7 +186,7 @@ def parse_expr(s: str): result = get_parser().parseString(s, parseAll=True) except ParseException as exc: format_and_raise_parse_error(exc) - raise AssertionError("unreachable") + raise AssertionError("unreachable") # noqa: B904 joined = result.asList() assert len(joined) == 1 @@ -204,9 +204,8 @@ def validate_value(value, key): not_primitive = value is not None and not isinstance(value, PRIMITIVES) not_foreach = key is not None and "foreach" not in key if not_primitive and not_foreach: - if isinstance(value, dict): - if key == "cmd": - return True + if isinstance(value, dict) and key == "cmd": + return True raise ParseError( f"Cannot interpolate data of type '{type(value).__name__}'" ) diff --git a/dvc/pathspec_math.py b/dvc/pathspec_math.py index b0541176e..276f9ac29 100644 --- a/dvc/pathspec_math.py +++ b/dvc/pathspec_math.py @@ -50,8 +50,7 @@ def change_rule(rule, rel): rule = f"!/{rel}{rule}" else: rule = f"/{rel}{rule}" - rule = normalize_file(rule) - return rule + return normalize_file(rule) def _change_dirname(dirname, pattern_list, new_dirname): diff --git a/dvc/proc/manager.py b/dvc/proc/manager.py index 8371f5bbd..b2b415443 100644 --- a/dvc/proc/manager.py +++ b/dvc/proc/manager.py @@ -38,7 +38,7 @@ class ProcessManager: with open(info_path, encoding="utf-8") as fobj: return ProcessInfo.from_dict(json.load(fobj)) except FileNotFoundError: - raise KeyError + raise KeyError # noqa: B904 @reraise(FileNotFoundError, KeyError) def __setitem__(self, key: str, value: "ProcessInfo"): @@ -83,13 +83,12 @@ class ProcessManager: def send_signal(self, name: str, sig: int): """Send `signal` to the specified named process.""" process_info = self[name] - if sys.platform == "win32": - if sig not in ( - signal.SIGTERM, - signal.CTRL_C_EVENT, - signal.CTRL_BREAK_EVENT, - ): - raise UnsupportedSignalError(sig) + if sys.platform == "win32" and sig not in ( + signal.SIGTERM, + signal.CTRL_C_EVENT, # pylint: disable=no-member + signal.CTRL_BREAK_EVENT, # pylint: disable=no-member + ): + raise UnsupportedSignalError(sig) def handle_closed_process(): logging.warning( @@ -105,10 +104,9 @@ class ProcessManager: handle_closed_process() raise except OSError as exc: - if sys.platform == "win32": - if exc.winerror == 87: - handle_closed_process() - raise ProcessLookupError from exc + if getattr(exc, "winerror", None) == 87: + handle_closed_process() + raise ProcessLookupError from exc raise def terminate(self, name: str): diff --git a/dvc/progress.py b/dvc/progress.py index 7ca37f05b..13e7a5ca9 100644 --- a/dvc/progress.py +++ b/dvc/progress.py @@ -46,7 +46,7 @@ class Tqdm(tqdm): desc=None, leave=False, bar_format=None, - bytes=False, # pylint: disable=redefined-builtin + bytes=False, # noqa: A002, pylint: disable=redefined-builtin file=None, total=None, postfix=None, diff --git a/dvc/render/converter/vega.py b/dvc/render/converter/vega.py index 8996d113e..74362c958 100644 --- a/dvc/render/converter/vega.py +++ b/dvc/render/converter/vega.py @@ -161,35 +161,35 @@ class VegaConverter(Converter): @staticmethod def infer_y_label(properties): y_label = properties.get("y_label", None) - if y_label is None: - y = properties.get("y", None) - if isinstance(y, dict): - fields = {field for _, field in _file_field(y)} - if len(fields) == 1: - y_label = first(fields) - else: - y_label = "y" - elif isinstance(y, list): - y_label = "y" - elif isinstance(y, str): - y_label = y + if y_label is not None: + return y_label + y = properties.get("y", None) + if isinstance(y, str): + return y + if isinstance(y, list): + return "y" + if not isinstance(y, dict): + return - return y_label + fields = {field for _, field in _file_field(y)} + if len(fields) == 1: + return first(fields) + return "y" @staticmethod def infer_x_label(properties): x_label = properties.get("x_label", None) - if x_label is None: - x = properties.get("x", None) - if isinstance(x, dict): - fields = {field for _, field in _file_field(x)} - if len(fields) == 1: - x_label = first(fields) - else: - x_label = "x" - else: - x_label = INDEX_FIELD - return x_label + if x_label is not None: + return x_label + + x = properties.get("x", None) + if not isinstance(x, dict): + return INDEX_FIELD + + fields = {field for _, field in _file_field(x)} + if len(fields) == 1: + return first(fields) + return "x" def flat_datapoints(self, revision): @@ -251,7 +251,7 @@ class VegaConverter(Converter): source_datapoints=x_datapoints, ) except IndexError: - raise DvcException( + raise DvcException( # noqa: B904 f"Cannot join '{x_field}' from '{x_file}' and " f"'{y_field}' from '{y_file}'. " "They have to have same length." diff --git a/dvc/repo/add.py b/dvc/repo/add.py index be341724c..52ad2821d 100644 --- a/dvc/repo/add.py +++ b/dvc/repo/add.py @@ -91,9 +91,11 @@ def translate_graph_error(stages: Stages) -> Iterator[None]: parent=exc.parent.fs_path, parent_stage=exc.parent.stage.addressing, ) - raise OverlappingOutputPathsError(exc.parent, exc.overlapping_out, msg) + raise OverlappingOutputPathsError( # noqa: B904 + exc.parent, exc.overlapping_out, msg + ) except OutputDuplicationError as exc: - raise OutputDuplicationError( + raise OutputDuplicationError( # noqa: B904 exc.output, list(set(exc.stages) - set(stages)) ) diff --git a/dvc/repo/experiments/apply.py b/dvc/repo/experiments/apply.py index 6eb2920c2..3fe2fce2b 100644 --- a/dvc/repo/experiments/apply.py +++ b/dvc/repo/experiments/apply.py @@ -47,7 +47,7 @@ def apply(repo: "Repo", rev: str, force: bool = True, **kwargs): baseline_sha = repo.scm.get_rev() if exp_ref_info: if baseline_sha != exp_ref_info.baseline_sha: - raise InvalidExpRevError(rev) + raise InvalidExpRevError(rev) # noqa: B904 exp_rev = repo.scm.get_ref(str(exp_ref_info)) elif queue_entry: if queue_entry.baseline_rev != baseline_sha: @@ -65,7 +65,7 @@ def apply(repo: "Repo", rev: str, force: bool = True, **kwargs): try: repo.scm.merge(exp_rev, commit=False, squash=True) except _SCMError as exc: - raise GitMergeError(str(exc), scm=repo.scm) + raise GitMergeError(str(exc), scm=repo.scm) # noqa: B904 repo.scm.reset() diff --git a/dvc/repo/experiments/branch.py b/dvc/repo/experiments/branch.py index addde1042..3d47e5946 100644 --- a/dvc/repo/experiments/branch.py +++ b/dvc/repo/experiments/branch.py @@ -19,7 +19,7 @@ def branch(repo, exp_rev, branch_name, *args, **kwargs): try: rev = resolve_rev(repo.scm, exp_rev) except RevError: - raise InvalidArgumentError(exp_rev) + raise InvalidArgumentError(exp_rev) # noqa: B904 ref_info = None ref_infos = list(exp_refs_by_rev(repo.scm, rev)) diff --git a/dvc/repo/experiments/executor/base.py b/dvc/repo/experiments/executor/base.py index 45ef7c3c4..c6a73561f 100644 --- a/dvc/repo/experiments/executor/base.py +++ b/dvc/repo/experiments/executor/base.py @@ -77,9 +77,8 @@ class ExecutorInfo: @classmethod def from_dict(cls, d): - if "collected" in d: - if d.pop("collected"): - d["status"] = TaskStatus.FINISHED + if d.pop("collected", None): + d["status"] = TaskStatus.FINISHED return cls(**d) def asdict(self): @@ -247,7 +246,7 @@ class BaseExecutor(ABC): root_dir: str, **kwargs, ) -> _T: - executor = cls( + return cls( root_dir=root_dir, dvc_dir=relpath(repo.dvc_dir, repo.scm.root_dir), baseline_rev=entry.baseline_rev, @@ -256,7 +255,6 @@ class BaseExecutor(ABC): wdir=relpath(os.getcwd(), repo.scm.root_dir), **kwargs, ) - return executor @staticmethod def hash_exp(stages: Iterable["PipelineStage"]) -> str: @@ -379,7 +377,7 @@ class BaseExecutor(ABC): try: dvc.scm.validate_git_remote(git_remote) except InvalidRemote as exc: - raise InvalidRemoteSCMRepo(str(exc)) + raise InvalidRemoteSCMRepo(str(exc)) # noqa: B904 dvc.cloud.get_remote_odb() @classmethod @@ -618,7 +616,7 @@ class BaseExecutor(ABC): push_cache=push_cache, run_cache=run_cache, ) - except BaseException as exc: # pylint: disable=broad-except + except BaseException as exc: # noqa: BLE001, pylint: disable=W0703 logger.warning( "Something went wrong while auto pushing experiment " "to the remote '%s': %s", diff --git a/dvc/repo/experiments/executor/local.py b/dvc/repo/experiments/executor/local.py index c2750c755..6aa7767a2 100644 --- a/dvc/repo/experiments/executor/local.py +++ b/dvc/repo/experiments/executor/local.py @@ -137,7 +137,7 @@ class TempDirExecutor(BaseLocalExecutor): try: self.scm.merge(merge_rev, squash=True, commit=False) except _SCMError as exc: - raise GitMergeError(str(exc), scm=self.scm) + raise GitMergeError(str(exc), scm=self.scm) # noqa: B904 def _config(self, cache_dir): local_config = os.path.join( @@ -227,7 +227,7 @@ class WorkspaceExecutor(BaseLocalExecutor): try: self.scm.merge(merge_rev, squash=True, commit=False) except _SCMError as exc: - raise GitMergeError(str(exc), scm=self.scm) + raise GitMergeError(str(exc), scm=self.scm) # noqa: B904 if branch: self.scm.set_ref(EXEC_BRANCH, branch, symbolic=True) elif scm.get_ref(EXEC_BRANCH): diff --git a/dvc/repo/experiments/executor/ssh.py b/dvc/repo/experiments/executor/ssh.py index 00e5e4226..55c04597f 100644 --- a/dvc/repo/experiments/executor/ssh.py +++ b/dvc/repo/experiments/executor/ssh.py @@ -112,11 +112,10 @@ class SSHExecutor(BaseExecutor): @staticmethod def _git_client_args(fs): - kwargs = { + return { "password": fs.fs_args.get("password"), "key_filename": first(fs.fs_args.get("client_keys", [])), } - return kwargs def init_git( self, @@ -243,8 +242,8 @@ class SSHExecutor(BaseExecutor): def reproduce( cls, info: "ExecutorInfo", - rev: str, - queue: Optional["Queue"] = None, + rev: str, # noqa: ARG003 + queue: Optional["Queue"] = None, # noqa: ARG003 infofile: Optional[str] = None, log_errors: bool = True, log_level: Optional[int] = None, diff --git a/dvc/repo/experiments/init.py b/dvc/repo/experiments/init.py index da1f56ebe..b16c6e9af 100644 --- a/dvc/repo/experiments/init.py +++ b/dvc/repo/experiments/init.py @@ -142,14 +142,13 @@ def validate_prompts( except MissingParamsFile: return value, msg_format.format(value, "file") except ParamsIsADirectoryError: - raise InvalidResponse( + raise InvalidResponse( # noqa: B904 f"[prompt.invalid]'{value}' is a directory. " "Please retry with an existing parameters file." ) - elif key in ("code", "data"): - if not os.path.exists(value): - typ = "file" if is_file(value) else "directory" - return value, msg_format.format(value, typ) + elif key in ("code", "data") and not os.path.exists(value): + typ = "file" if is_file(value) else "directory" + return value, msg_format.format(value, typ) return value @@ -200,7 +199,7 @@ def init_out_dirs(stage: PipelineStage) -> List[str]: def init( repo: "Repo", name: str = "train", - type: str = "default", # pylint: disable=redefined-builtin + type: str = "default", # noqa: A002, pylint: disable=redefined-builtin defaults: Dict[str, str] = None, overrides: Dict[str, str] = None, interactive: bool = False, @@ -244,7 +243,9 @@ def init( try: ParamsDependency(None, params, repo=repo).validate_filepath() except ParamsIsADirectoryError as exc: - raise DvcException(f"{exc}.") # swallow cause for display + raise DvcException( # noqa: B904 + f"{exc}." + ) # swallow cause for display except MissingParamsFile: pass diff --git a/dvc/repo/experiments/push.py b/dvc/repo/experiments/push.py index 5407170c3..768642082 100644 --- a/dvc/repo/experiments/push.py +++ b/dvc/repo/experiments/push.py @@ -96,7 +96,7 @@ def _push( progress=pbar.update_git, ) except AuthError as exc: - raise GitAuthError(str(exc)) + raise GitAuthError(str(exc)) # noqa: B904 def group_result(refspec): return results[str(refspec)] diff --git a/dvc/repo/experiments/queue/base.py b/dvc/repo/experiments/queue/base.py index 3abd986d6..3ea977234 100644 --- a/dvc/repo/experiments/queue/base.py +++ b/dvc/repo/experiments/queue/base.py @@ -508,7 +508,7 @@ class BaseStashQueue(ABC): with open(self.args_file, "rb") as fobj: try: data = pickle.load(fobj) - except Exception: # pylint: disable=broad-except + except Exception: # noqa: BLE001, pylint: disable=broad-except data = {} extra = int(data.get("extra", 0)) + 1 else: diff --git a/dvc/repo/experiments/queue/celery.py b/dvc/repo/experiments/queue/celery.py index ffec2530d..d935b125f 100644 --- a/dvc/repo/experiments/queue/celery.py +++ b/dvc/repo/experiments/queue/celery.py @@ -72,7 +72,7 @@ class LocalCeleryQueue(BaseStashQueue): @cached_property def celery(self) -> "FSApp": - from kombu.transport.filesystem import Channel # type: ignore + from kombu.transport.filesystem import Channel # related to https://github.com/iterative/dvc-task/issues/61 Channel.QoS.restore_at_shutdown = False @@ -232,10 +232,10 @@ class LocalCeleryQueue(BaseStashQueue): exp_result = self.get_result(entry) except FileNotFoundError: if result.status == "SUCCESS": - raise DvcException( + raise DvcException( # noqa: B904 f"Invalid experiment '{entry.stash_rev[:7]}'." ) - elif result.status == "FAILURE": + if result.status == "FAILURE": exp_result = None yield QueueDoneResult(entry, exp_result) @@ -421,7 +421,9 @@ class LocalCeleryQueue(BaseStashQueue): try: proc_info = self.proc[queue_entry.stash_rev] except KeyError: - raise DvcException(f"No output logs found for experiment '{rev}'") + raise DvcException( # noqa: B904 + f"No output logs found for experiment '{rev}'" + ) with open( proc_info.stdout, encoding=encoding or locale.getpreferredencoding(), diff --git a/dvc/repo/experiments/queue/tasks.py b/dvc/repo/experiments/queue/tasks.py index 7fcd99a0e..6e1f9a513 100644 --- a/dvc/repo/experiments/queue/tasks.py +++ b/dvc/repo/experiments/queue/tasks.py @@ -43,7 +43,7 @@ def setup_exp(entry_dict: Dict[str, Any]) -> "BaseExecutor": @shared_task def collect_exp( - proc_dict: Dict[str, Any], # pylint: disable=unused-argument + proc_dict: Dict[str, Any], # noqa: ARG001, pylint: disable=unused-argument entry_dict: Dict[str, Any], ) -> str: """Collect results for an experiment. @@ -73,7 +73,7 @@ def collect_exp( else: logger.debug("Experiment failed (Exec result was None)") celery_queue.stash_failed(entry) - except Exception: # pylint: disable=broad-except + except Exception: # noqa: BLE001, pylint: disable=broad-except # Log exceptions but do not re-raise so that task chain execution # continues logger.exception("Failed to collect experiment") diff --git a/dvc/repo/experiments/queue/tempdir.py b/dvc/repo/experiments/queue/tempdir.py index 2261216fb..99db5d6e7 100644 --- a/dvc/repo/experiments/queue/tempdir.py +++ b/dvc/repo/experiments/queue/tempdir.py @@ -130,7 +130,7 @@ class TempDirQueue(WorkspaceQueue): return results except DvcException: raise - except Exception as exc: + except Exception as exc: # noqa: BLE001 raise DvcException( f"Failed to reproduce experiment '{rev[:7]}'" ) from exc diff --git a/dvc/repo/experiments/queue/workspace.py b/dvc/repo/experiments/queue/workspace.py index 69c4fcc75..f51151a34 100644 --- a/dvc/repo/experiments/queue/workspace.py +++ b/dvc/repo/experiments/queue/workspace.py @@ -134,7 +134,7 @@ class WorkspaceQueue(BaseStashQueue): return {} except DvcException: raise - except Exception as exc: + except Exception as exc: # noqa: BLE001 raise DvcException( f"Failed to reproduce experiment '{rev[:7]}'" ) from exc @@ -152,7 +152,7 @@ class WorkspaceQueue(BaseStashQueue): @staticmethod def collect_executor( # pylint: disable=unused-argument exp: "Experiments", - executor: BaseExecutor, + executor: BaseExecutor, # noqa: ARG004 exec_result: ExecutorResult, ) -> Dict[str, str]: results: Dict[str, str] = {} diff --git a/dvc/repo/experiments/refs.py b/dvc/repo/experiments/refs.py index c7efc78b3..cca3f0e78 100644 --- a/dvc/repo/experiments/refs.py +++ b/dvc/repo/experiments/refs.py @@ -55,7 +55,7 @@ class ExpRefInfo: ): raise InvalidExpRefError(ref) except ValueError: - raise InvalidExpRefError(ref) + raise InvalidExpRefError(ref) # noqa: B904 baseline_sha = parts[2] + parts[3] name = parts[4] if len(parts) == 5 else None return cls(baseline_sha, name) diff --git a/dvc/repo/experiments/show.py b/dvc/repo/experiments/show.py index c2251528c..682332aa5 100644 --- a/dvc/repo/experiments/show.py +++ b/dvc/repo/experiments/show.py @@ -43,8 +43,7 @@ class ExpStatus(Enum): def _is_scm_error(collected_exp: Dict[str, Any]) -> bool: if "error" in collected_exp and ( - isinstance(collected_exp["error"], SCMError) - or isinstance(collected_exp["error"], InnerScmError) + isinstance(collected_exp["error"], (SCMError, InnerScmError)) ): return True return False @@ -135,7 +134,7 @@ def _collect_complete_experiment( ) -> Dict[str, Dict[str, Any]]: results: "OrderedDict[str, Dict[str, Any]]" = OrderedDict() - checkpoint: bool = True if len(revs) > 1 else False + checkpoint = len(revs) > 1 prev = "" for rev in revs: diff --git a/dvc/repo/experiments/utils.py b/dvc/repo/experiments/utils.py index 121e39387..15b2241e4 100644 --- a/dvc/repo/experiments/utils.py +++ b/dvc/repo/experiments/utils.py @@ -122,9 +122,9 @@ def iter_remote_refs( try: yield from scm.iter_remote_refs(url, base=base, **kwargs) except InvalidRemote as exc: - raise InvalidRemoteSCMRepo(str(exc)) + raise InvalidRemoteSCMRepo(str(exc)) # noqa: B904 except AuthError as exc: - raise GitAuthError(str(exc)) + raise GitAuthError(str(exc)) # noqa: B904 def push_refspec( @@ -169,7 +169,7 @@ def push_refspec( f"local ref '{diverged}' diverged from remote '{url}'" ) except AuthError as exc: - raise GitAuthError(str(exc)) + raise GitAuthError(str(exc)) # noqa: B904 def remote_exp_refs(scm: "Git", url: str) -> Generator[ExpRefInfo, None, None]: @@ -300,8 +300,8 @@ def fetch_all_exps(scm: "Git", url: str, progress: Optional[Callable] = None): def get_random_exp_name(scm, baseline_rev): # fmt: off - NOUNS = ('abac', 'abbs', 'aces', 'acid', 'acne', 'acre', 'acts', 'ados', 'adze', 'afro', 'agas', 'aged', 'ages', 'agio', 'agma', 'airs', 'airt', 'aits', 'akes', 'alap', 'albs', 'alga', 'ally', 'alto', 'amah', 'ambo', 'amie', 'amyl', 'ankh', 'apex', 'aqua', 'arcs', 'areg', 'aria', 'aril', 'arks', 'army', 'auks', 'aune', 'aura', 'awls', 'awns', 'axon', 'azan', 'baby', 'bade', 'bael', 'bags', 'bait', 'ball', 'banc', 'bang', 'bani', 'barb', 'bark', 'bate', 'bats', 'bawl', 'beak', 'bean', 'beep', 'belt', 'berk', 'beth', 'bias', 'bice', 'bids', 'bind', 'bise', 'bish', 'bite', 'boar', 'boat', 'body', 'boff', 'bold', 'boll', 'bolo', 'bomb', 'bond', 'book', 'boor', 'boot', 'bort', 'bosk', 'bots', 'bott', 'bout', 'bras', 'bree', 'brig', 'brio', 'buck', 'buhl', 'bump', 'bunk', 'bunt', 'buoy', 'byes', 'byte', 'cane', 'cant', 'caps', 'care', 'cart', 'cats', 'cedi', 'ceps', 'cere', 'chad', 'cham', 'chat', 'chay', 'chic', 'chin', 'chis', 'chiv', 'choc', 'chow', 'chum', 'ciao', 'cigs', 'clay', 'clip', 'clog', 'coal', 'coat', 'code', 'coed', 'cogs', 'coho', 'cole', 'cols', 'colt', 'conk', 'cons', 'cony', 'coof', 'cook', 'cool', 'coos', 'corm', 'cors', 'coth', 'cows', 'coze', 'crag', 'craw', 'cree', 'crib', 'cuds', 'cull', 'cult', 'curb', 'curn', 'curs', 'cusp', 'cuss', 'cwms', 'cyma', 'cyst', 'dabs', 'dado', 'daff', 'dais', 'daks', 'damn', 'dams', 'darg', 'dart', 'data', 'dawk', 'dawn', 'daws', 'daze', 'dean', 'debs', 'debt', 'deep', 'dees', 'dele', 'delf', 'dent', 'deys', 'dhow', 'digs', 'dirk', 'dita', 'diva', 'divs', 'doek', 'doge', 'dogs', 'dogy', 'dohs', 'doit', 'dole', 'doll', 'dolt', 'dona', 'dook', 'door', 'dops', 'doss', 'doxy', 'drab', 'drop', 'drum', 'duad', 'duct', 'duff', 'duke', 'dunk', 'dunt', 'ears', 'ease', 'eggs', 'eild', 'emeu', 'emus', 'envy', 'epha', 'eric', 'erns', 'esne', 'esse', 'ewes', 'expo', 'eyas', 'eyot', 'eyry', 'fare', 'farl', 'farm', 'feds', 'feel', 'fees', 'feme', 'fess', 'fibs', 'fids', 'fils', 'firm', 'fish', 'flab', 'flap', 'flea', 'flew', 'flex', 'flip', 'flit', 'flus', 'flux', 'foil', 'fond', 'food', 'fool', 'ford', 'fore', 'frit', 'friz', 'froe', 'funs', 'furl', 'fuss', 'fuzz', 'gaby', 'gaff', 'gale', 'gang', 'gaol', 'gape', 'gash', 'gaur', 'gaze', 'gear', 'genu', 'gest', 'geum', 'ghat', 'gigs', 'gimp', 'gird', 'girl', 'glee', 'glen', 'glia', 'glop', 'gnat', 'goad', 'goaf', 'gobs', 'gonk', 'good', 'goos', 'gore', 'gram', 'gray', 'grig', 'grip', 'grot', 'grub', 'gude', 'gula', 'gulf', 'guns', 'gust', 'gyms', 'gyro', 'hack', 'haet', 'hajj', 'hake', 'half', 'halm', 'hard', 'harl', 'hask', 'hate', "he'd", 'heck', 'heel', 'heir', 'help', 'hems', 'here', 'hill', 'hips', 'hits', 'hobo', 'hock', 'hogs', 'hold', 'holy', 'hood', 'hoot', 'hope', 'horn', 'hose', 'hour', 'hows', 'huck', 'hugs', 'huia', 'hulk', 'hull', 'hunk', 'hunt', 'huts', 'hymn', 'ibex', 'ices', 'iglu', 'impi', 'inks', 'inti', 'ions', 'iota', 'iron', 'jabs', 'jags', 'jake', 'jass', 'jato', 'jaws', 'jean', 'jeer', 'jerk', 'jest', 'jiao', 'jigs', 'jill', 'jinn', 'jird', 'jive', 'jock', 'joey', 'jogs', 'joss', 'jota', 'jots', 'juba', 'jube', 'judo', 'jump', 'junk', 'jura', 'juts', 'jynx', 'kago', 'kail', 'kaka', 'kale', 'kana', 'keek', 'keep', 'kefs', 'kegs', 'kerf', 'kern', 'keys', 'kibe', 'kick', 'kids', 'kifs', 'kill', 'kina', 'kind', 'kine', 'kite', 'kiwi', 'knap', 'knit', 'koas', 'kobs', 'kyat', 'lack', 'lahs', 'lair', 'lama', 'lamb', 'lame', 'lats', 'lava', 'lays', 'leaf', 'leak', 'leas', 'lees', 'leks', 'leno', 'libs', 'lich', 'lick', 'lien', 'lier', 'lieu', 'life', 'lift', 'limb', 'line', 'link', 'linn', 'lira', 'loft', 'loge', 'loir', 'long', 'loof', 'look', 'loot', 'lore', 'loss', 'lots', 'loup', 'love', 'luce', 'ludo', 'luke', 'lulu', 'lure', 'lush', 'magi', 'maid', 'main', 'mako', 'male', 'mana', 'many', 'mart', 'mash', 'mast', 'mate', 'math', 'mats', 'matt', 'maul', 'maya', 'mays', 'meal', 'mean', 'meed', 'mela', 'mene', 'mere', 'merk', 'mesh', 'mete', 'mice', 'milo', 'mime', 'mina', 'mine', 'mirk', 'miss', 'mobs', 'moit', 'mold', 'molt', 'mome', 'moms', 'monk', 'moot', 'mope', 'more', 'morn', 'mows', 'moxa', 'much', 'mung', 'mush', 'muss', 'myth', 'name', 'nard', 'nark', 'nave', 'navy', 'neck', 'newt', 'nibs', 'nims', 'nine', 'nock', 'noil', 'noma', 'nosh', 'nowt', 'nuke', 'oafs', 'oast', 'oats', 'obit', 'odor', 'okra', 'omer', 'oner', 'ones', 'orcs', 'ords', 'orfe', 'orle', 'ossa', 'outs', 'over', 'owls', 'pail', 'pall', 'palp', 'pams', 'pang', 'pans', 'pant', 'paps', 'pate', 'pats', 'paws', 'pear', 'peba', 'pech', 'pecs', 'peel', 'peer', 'pees', 'pein', 'peri', 'phon', 'pice', 'pita', 'pith', 'play', 'plop', 'plot', 'plow', 'plug', 'plum', 'polo', 'pomp', 'pond', 'pons', 'pony', 'poof', 'poon', 'pope', 'porn', 'poss', 'pots', 'pour', 'prad', 'prat', 'prep', 'prob', 'prof', 'prow', 'puck', 'puds', 'puke', 'puku', 'pump', 'puns', 'pupa', 'purl', 'pyre', 'quad', 'quay', 'quey', 'quiz', 'raid', 'rail', 'rain', 'raja', 'rale', 'rams', 'rand', 'rant', 'raps', 'rasp', 'razz', 'rede', 'reef', 'reif', 'rein', 'repp', 'rial', 'ribs', 'rick', 'rift', 'rill', 'rime', 'rims', 'ring', 'rins', 'rise', 'rite', 'rits', 'roam', 'robe', 'rods', 'roma', 'rook', 'rort', 'rotl', 'roup', 'roux', 'rube', 'rubs', 'ruby', 'rues', 'rugs', 'ruin', 'runs', 'ryas', 'sack', 'sacs', 'saga', 'sail', 'sale', 'salp', 'salt', 'sand', 'sang', 'sash', 'saut', 'says', 'scab', 'scow', 'scud', 'scup', 'scut', 'seal', 'seam', 'sech', 'seed', 'seep', 'seer', 'self', 'sena', 'send', 'sera', 'sere', 'shad', 'shah', 'sham', 'shay', 'shes', 'ship', 'shoe', 'sick', 'sida', 'sign', 'sike', 'sima', 'sine', 'sing', 'sinh', 'sink', 'sins', 'site', 'size', 'skat', 'skin', 'skip', 'skis', 'slaw', 'sled', 'slew', 'sley', 'slob', 'slue', 'slug', 'smut', 'snap', 'snib', 'snip', 'snob', 'snog', 'snot', 'snow', 'snub', 'snug', 'soft', 'soja', 'soke', 'song', 'sons', 'sook', 'sorb', 'sori', 'souk', 'soul', 'sous', 'soya', 'spit', 'stay', 'stew', 'stir', 'stob', 'stud', 'suds', 'suer', 'suit', 'sumo', 'sums', 'sups', 'suqs', 'suss', 'sway', 'syce', 'synd', 'taal', 'tach', 'taco', 'tads', 'taka', 'tale', 'tamp', 'tams', 'tang', 'tans', 'tape', 'tare', 'taro', 'tarp', 'tart', 'tass', 'taus', 'teat', 'teds', 'teff', 'tegu', 'tell', 'term', 'thar', 'thaw', 'tics', 'tier', 'tiff', 'tils', 'tilt', 'tint', 'tipi', 'tire', 'tirl', 'toby', 'tods', 'toea', 'toff', 'toga', 'toil', 'toke', 'tola', 'tole', 'tomb', 'toms', 'torc', 'tors', 'tort', 'tosh', 'tote', 'tret', 'trey', 'trio', 'trug', 'tuck', 'tugs', 'tule', 'tune', 'tuns', 'tuts', 'tyke', 'tyne', 'typo', 'ulna', 'umbo', 'unau', 'unit', 'upas', 'user', 'uvea', 'vacs', 'vane', 'vang', 'vans', 'vara', 'vase', 'veep', 'veer', 'vega', 'veil', 'vela', 'vent', 'vies', 'view', 'vina', 'vine', 'vise', 'vlei', 'volt', 'vows', 'wads', 'waft', 'wage', 'wain', 'walk', 'want', 'wart', 'wave', 'waws', 'weal', 'wean', 'weds', 'weep', 'weft', 'weir', 'weka', 'weld', 'wens', 'weys', 'whap', 'whey', 'whin', 'whit', 'whop', 'wide', 'wife', 'wind', 'wine', 'wino', 'wins', 'wire', 'wise', 'woes', 'wont', 'wool', 'work', 'worm', 'wort', 'yack', 'yank', 'yapp', 'yard', 'yate', 'yawl', 'yegg', 'yell', 'yeuk', 'yews', 'yips', 'yobs', 'yogi', 'yoke', 'yolk', 'yoni', 'zack', 'zags', 'zest', 'zhos', 'zigs', 'zila', 'zips', 'ziti', 'zoea', 'zone', 'zoon') # noqa: E501 - ADJECTIVES = ('about', 'above', 'abuzz', 'acerb', 'acold', 'acred', 'added', 'addle', 'adept', 'adult', 'adunc', 'adust', 'afoul', 'after', 'agape', 'agaze', 'agile', 'aging', 'agley', 'aglow', 'ahead', 'ahull', 'aided', 'alary', 'algal', 'alike', 'alive', 'alone', 'aloof', 'alpha', 'amber', 'amiss', 'amort', 'ample', 'amuck', 'angry', 'anile', 'apeak', 'apish', 'arced', 'areal', 'armed', 'aroid', 'ashen', 'aspen', 'astir', 'atilt', 'atrip', 'aulic', 'aural', 'awash', 'awful', 'awing', 'awned', 'axile', 'azoic', 'azure', 'baggy', 'baked', 'balky', 'bally', 'balmy', 'banal', 'bandy', 'bardy', 'bared', 'barer', 'barky', 'basal', 'based', 'baser', 'basic', 'batty', 'bawdy', 'beady', 'beaky', 'beamy', 'beaut', 'beefy', 'beery', 'beige', 'bendy', 'bifid', 'bijou', 'biped', 'birch', 'bitty', 'blame', 'bland', 'blank', 'blear', 'blest', 'blind', 'blond', 'blown', 'blowy', 'bluer', 'bluff', 'blunt', 'boned', 'bonny', 'boozy', 'bored', 'boric', 'bosky', 'bosom', 'bound', 'bovid', 'bowed', 'boxed', 'braky', 'brash', 'brief', 'briny', 'brisk', 'broad', 'broch', 'brood', 'brown', 'brute', 'buggy', 'bulgy', 'bumpy', 'burly', 'burnt', 'burry', 'bushy', 'busty', 'butch', 'buxom', 'cadgy', 'cagey', 'calmy', 'campy', 'canny', 'caped', 'cased', 'catty', 'cauld', 'cedar', 'cered', 'ceric', 'chary', 'cheap', 'cheek', 'chewy', 'chief', 'chill', 'chirk', 'choky', 'cissy', 'civil', 'cleft', 'coaly', 'color', 'comfy', 'comic', 'compo', 'conic', 'couth', 'coxal', 'crack', 'crank', 'crash', 'crass', 'crisp', 'cronk', 'cross', 'crude', 'cruel', 'crumb', 'cured', 'curly', 'curst', 'cushy', 'cutty', 'cynic', 'dated', 'dazed', 'dedal', 'deism', 'diazo', 'dicey', 'dingy', 'direr', 'dirty', 'dishy', 'dizzy', 'dolce', 'doped', 'dopey', 'dormy', 'dorty', 'dosed', 'dotal', 'dotty', 'dowdy', 'dowie', 'downy', 'dozen', 'drawn', 'dread', 'drear', 'dress', 'dried', 'ducky', 'duddy', 'dummy', 'dumpy', 'duple', 'dural', 'dusky', 'dusty', 'dutch', 'dying', 'eager', 'eaten', 'ebony', 'edged', 'eerie', 'eight', 'elder', 'elect', 'elfin', 'elite', 'empty', 'enate', 'enemy', 'epoxy', 'erect', 'ethic', 'every', 'extra', 'faced', 'faery', 'faint', 'famed', 'fancy', 'farci', 'fatal', 'fated', 'fatty', 'fazed', 'felon', 'fenny', 'ferny', 'fetal', 'fetid', 'fewer', 'fiery', 'fifty', 'filar', 'filmy', 'final', 'fined', 'finer', 'finny', 'fired', 'first', 'fishy', 'fixed', 'fizzy', 'flaky', 'flamy', 'flash', 'flawy', 'fleet', 'flory', 'flown', 'fluid', 'fluky', 'flush', 'focal', 'foggy', 'folio', 'forky', 'forte', 'forty', 'found', 'frail', 'frank', 'freed', 'freer', 'fresh', 'fried', 'front', 'frore', 'fuggy', 'funky', 'funny', 'furry', 'fusil', 'fussy', 'fuzzy', 'gabby', 'gamer', 'gamey', 'gamic', 'gammy', 'garni', 'gauge', 'gaunt', 'gauzy', 'gawky', 'gawsy', 'gemmy', 'genal', 'genic', 'ghast', 'gimpy', 'girly', 'glare', 'glary', 'glial', 'glued', 'gluey', 'godly', 'gooey', 'goofy', 'goosy', 'gouty', 'grade', 'grand', 'grapy', 'grave', 'gross', 'group', 'gruff', 'guest', 'gules', 'gulfy', 'gummy', 'gushy', 'gusty', 'gutsy', 'gutta', 'gypsy', 'gyral', 'hadal', 'hammy', 'handy', 'hardy', 'hasty', 'hated', 'hazel', 'heady', 'heapy', 'hefty', 'heigh', 'hempy', 'herby', 'hexed', 'hi-fi', 'hilly', 'hired', 'holey', 'honey', 'hooly', 'hoven', 'huger', 'hulky', 'humid', 'hunky', 'hyoid', 'idled', 'iliac', 'inane', 'incog', 'inert', 'inner', 'inter', 'iodic', 'ionic', 'irate', 'irony', 'itchy', 'jaggy', 'jammy', 'japan', 'jazzy', 'jerky', 'jetty', 'joint', 'jowly', 'juicy', 'jumpy', 'jural', 'kacha', 'kaput', 'kempt', 'keyed', 'kinky', 'known', 'kooky', 'kraal', 'laced', 'laigh', 'lairy', 'lamer', 'lardy', 'larky', 'lated', 'later', 'lathy', 'leady', 'leafy', 'leaky', 'leary', 'least', 'ledgy', 'leery', 'legal', 'leggy', 'lento', 'level', 'licht', 'licit', 'liege', 'light', 'liked', 'liney', 'lippy', 'lived', 'livid', 'loamy', 'loath', 'lobar', 'local', 'loony', 'loose', 'loral', 'losel', 'lousy', 'loved', 'lower', 'lowly', 'lowse', 'loyal', 'lucid', 'lucky', 'lumpy', 'lunar', 'lurid', 'lushy', 'lying', 'lyric', 'macho', 'macro', 'magic', 'major', 'malar', 'mangy', 'manky', 'manly', 'mardy', 'massy', 'mated', 'matte', 'mauve', 'mazed', 'mealy', 'meaty', 'medal', 'melic', 'mesic', 'mesne', 'messy', 'metal', 'miffy', 'milky', 'mined', 'minim', 'minor', 'minus', 'mired', 'mirky', 'misty', 'mixed', 'modal', 'model', 'moire', 'molar', 'moldy', 'moody', 'moony', 'mopey', 'moral', 'mossy', 'mothy', 'motor', 'mousy', 'moved', 'mucid', 'mucky', 'muddy', 'muggy', 'muley', 'mural', 'murky', 'mushy', 'muted', 'muzzy', 'myoid', 'naggy', 'naive', 'naked', 'named', 'nasty', 'natal', 'naval', 'nervy', 'newsy', 'nicer', 'niffy', 'nifty', 'ninth', 'nitty', 'nival', 'noble', 'nodal', 'noisy', 'non-U', 'north', 'nosed', 'noted', 'nowed', 'nubby', 'oaken', 'oared', 'oaten', 'obese', 'ocher', 'ochre', 'often', 'ohmic', 'oiled', 'olden', 'older', 'oleic', 'olive', 'optic', 'ortho', 'osmic', 'other', 'outer', 'ovoid', 'owing', 'owned', 'paced', 'pagan', 'paled', 'paler', 'pally', 'paper', 'pappy', 'parky', 'party', 'pasty', 'pavid', 'pawky', 'peaky', 'pearl', 'peart', 'peaty', 'pedal', 'peppy', 'perdu', 'perky', 'pesky', 'phony', 'piano', 'picky', 'piled', 'piney', 'pious', 'pique', 'pithy', 'platy', 'plump', 'plush', 'podgy', 'potty', 'power', 'prest', 'pricy', 'prima', 'prime', 'print', 'privy', 'prize', 'prone', 'proof', 'prosy', 'proud', 'proxy', 'pseud', 'pucka', 'pudgy', 'puffy', 'pukka', 'pupal', 'purer', 'pursy', 'pushy', 'pyoid', 'quack', 'quare', 'quasi', 'quiet', 'quits', 'rabic', 'rabid', 'radio', 'raked', 'randy', 'rapid', 'rarer', 'raspy', 'rathe', 'ratty', 'ready', 'reedy', 'reeky', 'refer', 'regal', 'riant', 'ridgy', 'right', 'riled', 'rimed', 'rindy', 'risen', 'risky', 'ritzy', 'rival', 'riven', 'robed', 'rocky', 'roily', 'roman', 'rooky', 'ropey', 'round', 'rowdy', 'ruddy', 'ruled', 'rummy', 'runic', 'runny', 'runty', 'rural', 'rusty', 'rutty', 'sable', 'salic', 'sandy', 'sappy', 'sarky', 'sassy', 'sated', 'saved', 'savvy', 'scald', 'scaly', 'scary', 'score', 'scrap', 'sedgy', 'seely', 'seral', 'sewed', 'shaky', 'sharp', 'sheen', 'shier', 'shill', 'shoal', 'shock', 'shoed', 'shore', 'short', 'shyer', 'silky', 'silly', 'silty', 'sixth', 'sixty', 'skint', 'slack', 'slant', 'sleek', 'slier', 'slimy', 'slung', 'small', 'smart', 'smoky', 'snaky', 'sneak', 'snide', 'snowy', 'snuff', 'so-so', 'soapy', 'sober', 'socko', 'solar', 'soled', 'solid', 'sonic', 'sooth', 'sooty', 'soppy', 'sorer', 'sound', 'soupy', 'spent', 'spicy', 'spiky', 'spiny', 'spiry', 'splay', 'split', 'sport', 'spumy', 'squat', 'staid', 'stiff', 'still', 'stoic', 'stone', 'stony', 'store', 'stout', 'straw', 'stray', 'strip', 'stung', 'suave', 'sudsy', 'sulfa', 'sulky', 'sunny', 'super', 'sural', 'surer', 'surfy', 'surgy', 'surly', 'swell', 'swept', 'swish', 'sworn', 'tabby', 'taboo', 'tacit', 'tacky', 'tamed', 'tamer', 'tangy', 'taped', 'tarot', 'tarry', 'tasty', 'tatty', 'taunt', 'tawie', 'teary', 'techy', 'telic', 'tenor', 'tense', 'tenth', 'tenty', 'tepid', 'terse', 'testy', 'third', 'tidal', 'tight', 'tiled', 'timid', 'tinct', 'tined', 'tippy', 'tipsy', 'tonal', 'toned', 'tonic', 'toric', 'total', 'tough', 'toxic', 'trade', 'treed', 'treen', 'trial', 'truer', 'tubal', 'tubby', 'tumid', 'tuned', 'tutti', 'twill', 'typal', 'typed', 'typic', 'umber', 'unapt', 'unbid', 'uncut', 'undue', 'undug', 'unfed', 'unfit', 'union', 'unlet', 'unmet', 'unwed', 'unwet', 'upper', 'upset', 'urban', 'utile', 'uveal', 'vagal', 'valid', 'vapid', 'varus', 'vatic', 'veiny', 'vital', 'vivid', 'vocal', 'vogie', 'volar', 'vying', 'wacky', 'wally', 'waney', 'warty', 'washy', 'waspy', 'waste', 'waugh', 'waxen', 'webby', 'wedgy', 'weeny', 'weepy', 'weest', 'weird', 'welsh', 'wersh', 'whist', 'white', 'whity', 'whole', 'wider', 'wight', 'winey', 'wired', 'wised', 'wiser', 'withy', 'wonky', 'woods', 'woozy', 'world', 'wormy', 'worse', 'worst', 'woven', 'wrath', 'wrier', 'wrong', 'wroth', 'xeric', 'yarer', 'yolky', 'young', 'yucky', 'yummy', 'zesty', 'zingy', 'zinky', 'zippy', 'zonal') # noqa: E501 + NOUNS = ('abac', 'abbs', 'aces', 'acid', 'acne', 'acre', 'acts', 'ados', 'adze', 'afro', 'agas', 'aged', 'ages', 'agio', 'agma', 'airs', 'airt', 'aits', 'akes', 'alap', 'albs', 'alga', 'ally', 'alto', 'amah', 'ambo', 'amie', 'amyl', 'ankh', 'apex', 'aqua', 'arcs', 'areg', 'aria', 'aril', 'arks', 'army', 'auks', 'aune', 'aura', 'awls', 'awns', 'axon', 'azan', 'baby', 'bade', 'bael', 'bags', 'bait', 'ball', 'banc', 'bang', 'bani', 'barb', 'bark', 'bate', 'bats', 'bawl', 'beak', 'bean', 'beep', 'belt', 'berk', 'beth', 'bias', 'bice', 'bids', 'bind', 'bise', 'bish', 'bite', 'boar', 'boat', 'body', 'boff', 'bold', 'boll', 'bolo', 'bomb', 'bond', 'book', 'boor', 'boot', 'bort', 'bosk', 'bots', 'bott', 'bout', 'bras', 'bree', 'brig', 'brio', 'buck', 'buhl', 'bump', 'bunk', 'bunt', 'buoy', 'byes', 'byte', 'cane', 'cant', 'caps', 'care', 'cart', 'cats', 'cedi', 'ceps', 'cere', 'chad', 'cham', 'chat', 'chay', 'chic', 'chin', 'chis', 'chiv', 'choc', 'chow', 'chum', 'ciao', 'cigs', 'clay', 'clip', 'clog', 'coal', 'coat', 'code', 'coed', 'cogs', 'coho', 'cole', 'cols', 'colt', 'conk', 'cons', 'cony', 'coof', 'cook', 'cool', 'coos', 'corm', 'cors', 'coth', 'cows', 'coze', 'crag', 'craw', 'cree', 'crib', 'cuds', 'cull', 'cult', 'curb', 'curn', 'curs', 'cusp', 'cuss', 'cwms', 'cyma', 'cyst', 'dabs', 'dado', 'daff', 'dais', 'daks', 'damn', 'dams', 'darg', 'dart', 'data', 'dawk', 'dawn', 'daws', 'daze', 'dean', 'debs', 'debt', 'deep', 'dees', 'dele', 'delf', 'dent', 'deys', 'dhow', 'digs', 'dirk', 'dita', 'diva', 'divs', 'doek', 'doge', 'dogs', 'dogy', 'dohs', 'doit', 'dole', 'doll', 'dolt', 'dona', 'dook', 'door', 'dops', 'doss', 'doxy', 'drab', 'drop', 'drum', 'duad', 'duct', 'duff', 'duke', 'dunk', 'dunt', 'ears', 'ease', 'eggs', 'eild', 'emeu', 'emus', 'envy', 'epha', 'eric', 'erns', 'esne', 'esse', 'ewes', 'expo', 'eyas', 'eyot', 'eyry', 'fare', 'farl', 'farm', 'feds', 'feel', 'fees', 'feme', 'fess', 'fibs', 'fids', 'fils', 'firm', 'fish', 'flab', 'flap', 'flea', 'flew', 'flex', 'flip', 'flit', 'flus', 'flux', 'foil', 'fond', 'food', 'fool', 'ford', 'fore', 'frit', 'friz', 'froe', 'funs', 'furl', 'fuss', 'fuzz', 'gaby', 'gaff', 'gale', 'gang', 'gaol', 'gape', 'gash', 'gaur', 'gaze', 'gear', 'genu', 'gest', 'geum', 'ghat', 'gigs', 'gimp', 'gird', 'girl', 'glee', 'glen', 'glia', 'glop', 'gnat', 'goad', 'goaf', 'gobs', 'gonk', 'good', 'goos', 'gore', 'gram', 'gray', 'grig', 'grip', 'grot', 'grub', 'gude', 'gula', 'gulf', 'guns', 'gust', 'gyms', 'gyro', 'hack', 'haet', 'hajj', 'hake', 'half', 'halm', 'hard', 'harl', 'hask', 'hate', "he'd", 'heck', 'heel', 'heir', 'help', 'hems', 'here', 'hill', 'hips', 'hits', 'hobo', 'hock', 'hogs', 'hold', 'holy', 'hood', 'hoot', 'hope', 'horn', 'hose', 'hour', 'hows', 'huck', 'hugs', 'huia', 'hulk', 'hull', 'hunk', 'hunt', 'huts', 'hymn', 'ibex', 'ices', 'iglu', 'impi', 'inks', 'inti', 'ions', 'iota', 'iron', 'jabs', 'jags', 'jake', 'jass', 'jato', 'jaws', 'jean', 'jeer', 'jerk', 'jest', 'jiao', 'jigs', 'jill', 'jinn', 'jird', 'jive', 'jock', 'joey', 'jogs', 'joss', 'jota', 'jots', 'juba', 'jube', 'judo', 'jump', 'junk', 'jura', 'juts', 'jynx', 'kago', 'kail', 'kaka', 'kale', 'kana', 'keek', 'keep', 'kefs', 'kegs', 'kerf', 'kern', 'keys', 'kibe', 'kick', 'kids', 'kifs', 'kill', 'kina', 'kind', 'kine', 'kite', 'kiwi', 'knap', 'knit', 'koas', 'kobs', 'kyat', 'lack', 'lahs', 'lair', 'lama', 'lamb', 'lame', 'lats', 'lava', 'lays', 'leaf', 'leak', 'leas', 'lees', 'leks', 'leno', 'libs', 'lich', 'lick', 'lien', 'lier', 'lieu', 'life', 'lift', 'limb', 'line', 'link', 'linn', 'lira', 'loft', 'loge', 'loir', 'long', 'loof', 'look', 'loot', 'lore', 'loss', 'lots', 'loup', 'love', 'luce', 'ludo', 'luke', 'lulu', 'lure', 'lush', 'magi', 'maid', 'main', 'mako', 'male', 'mana', 'many', 'mart', 'mash', 'mast', 'mate', 'math', 'mats', 'matt', 'maul', 'maya', 'mays', 'meal', 'mean', 'meed', 'mela', 'mene', 'mere', 'merk', 'mesh', 'mete', 'mice', 'milo', 'mime', 'mina', 'mine', 'mirk', 'miss', 'mobs', 'moit', 'mold', 'molt', 'mome', 'moms', 'monk', 'moot', 'mope', 'more', 'morn', 'mows', 'moxa', 'much', 'mung', 'mush', 'muss', 'myth', 'name', 'nard', 'nark', 'nave', 'navy', 'neck', 'newt', 'nibs', 'nims', 'nine', 'nock', 'noil', 'noma', 'nosh', 'nowt', 'nuke', 'oafs', 'oast', 'oats', 'obit', 'odor', 'okra', 'omer', 'oner', 'ones', 'orcs', 'ords', 'orfe', 'orle', 'ossa', 'outs', 'over', 'owls', 'pail', 'pall', 'palp', 'pams', 'pang', 'pans', 'pant', 'paps', 'pate', 'pats', 'paws', 'pear', 'peba', 'pech', 'pecs', 'peel', 'peer', 'pees', 'pein', 'peri', 'phon', 'pice', 'pita', 'pith', 'play', 'plop', 'plot', 'plow', 'plug', 'plum', 'polo', 'pomp', 'pond', 'pons', 'pony', 'poof', 'poon', 'pope', 'porn', 'poss', 'pots', 'pour', 'prad', 'prat', 'prep', 'prob', 'prof', 'prow', 'puck', 'puds', 'puke', 'puku', 'pump', 'puns', 'pupa', 'purl', 'pyre', 'quad', 'quay', 'quey', 'quiz', 'raid', 'rail', 'rain', 'raja', 'rale', 'rams', 'rand', 'rant', 'raps', 'rasp', 'razz', 'rede', 'reef', 'reif', 'rein', 'repp', 'rial', 'ribs', 'rick', 'rift', 'rill', 'rime', 'rims', 'ring', 'rins', 'rise', 'rite', 'rits', 'roam', 'robe', 'rods', 'roma', 'rook', 'rort', 'rotl', 'roup', 'roux', 'rube', 'rubs', 'ruby', 'rues', 'rugs', 'ruin', 'runs', 'ryas', 'sack', 'sacs', 'saga', 'sail', 'sale', 'salp', 'salt', 'sand', 'sang', 'sash', 'saut', 'says', 'scab', 'scow', 'scud', 'scup', 'scut', 'seal', 'seam', 'sech', 'seed', 'seep', 'seer', 'self', 'sena', 'send', 'sera', 'sere', 'shad', 'shah', 'sham', 'shay', 'shes', 'ship', 'shoe', 'sick', 'sida', 'sign', 'sike', 'sima', 'sine', 'sing', 'sinh', 'sink', 'sins', 'site', 'size', 'skat', 'skin', 'skip', 'skis', 'slaw', 'sled', 'slew', 'sley', 'slob', 'slue', 'slug', 'smut', 'snap', 'snib', 'snip', 'snob', 'snog', 'snot', 'snow', 'snub', 'snug', 'soft', 'soja', 'soke', 'song', 'sons', 'sook', 'sorb', 'sori', 'souk', 'soul', 'sous', 'soya', 'spit', 'stay', 'stew', 'stir', 'stob', 'stud', 'suds', 'suer', 'suit', 'sumo', 'sums', 'sups', 'suqs', 'suss', 'sway', 'syce', 'synd', 'taal', 'tach', 'taco', 'tads', 'taka', 'tale', 'tamp', 'tams', 'tang', 'tans', 'tape', 'tare', 'taro', 'tarp', 'tart', 'tass', 'taus', 'teat', 'teds', 'teff', 'tegu', 'tell', 'term', 'thar', 'thaw', 'tics', 'tier', 'tiff', 'tils', 'tilt', 'tint', 'tipi', 'tire', 'tirl', 'toby', 'tods', 'toea', 'toff', 'toga', 'toil', 'toke', 'tola', 'tole', 'tomb', 'toms', 'torc', 'tors', 'tort', 'tosh', 'tote', 'tret', 'trey', 'trio', 'trug', 'tuck', 'tugs', 'tule', 'tune', 'tuns', 'tuts', 'tyke', 'tyne', 'typo', 'ulna', 'umbo', 'unau', 'unit', 'upas', 'user', 'uvea', 'vacs', 'vane', 'vang', 'vans', 'vara', 'vase', 'veep', 'veer', 'vega', 'veil', 'vela', 'vent', 'vies', 'view', 'vina', 'vine', 'vise', 'vlei', 'volt', 'vows', 'wads', 'waft', 'wage', 'wain', 'walk', 'want', 'wart', 'wave', 'waws', 'weal', 'wean', 'weds', 'weep', 'weft', 'weir', 'weka', 'weld', 'wens', 'weys', 'whap', 'whey', 'whin', 'whit', 'whop', 'wide', 'wife', 'wind', 'wine', 'wino', 'wins', 'wire', 'wise', 'woes', 'wont', 'wool', 'work', 'worm', 'wort', 'yack', 'yank', 'yapp', 'yard', 'yate', 'yawl', 'yegg', 'yell', 'yeuk', 'yews', 'yips', 'yobs', 'yogi', 'yoke', 'yolk', 'yoni', 'zack', 'zags', 'zest', 'zhos', 'zigs', 'zila', 'zips', 'ziti', 'zoea', 'zone', 'zoon') # noqa: E501,Q000 + ADJECTIVES = ('about', 'above', 'abuzz', 'acerb', 'acold', 'acred', 'added', 'addle', 'adept', 'adult', 'adunc', 'adust', 'afoul', 'after', 'agape', 'agaze', 'agile', 'aging', 'agley', 'aglow', 'ahead', 'ahull', 'aided', 'alary', 'algal', 'alike', 'alive', 'alone', 'aloof', 'alpha', 'amber', 'amiss', 'amort', 'ample', 'amuck', 'angry', 'anile', 'apeak', 'apish', 'arced', 'areal', 'armed', 'aroid', 'ashen', 'aspen', 'astir', 'atilt', 'atrip', 'aulic', 'aural', 'awash', 'awful', 'awing', 'awned', 'axile', 'azoic', 'azure', 'baggy', 'baked', 'balky', 'bally', 'balmy', 'banal', 'bandy', 'bardy', 'bared', 'barer', 'barky', 'basal', 'based', 'baser', 'basic', 'batty', 'bawdy', 'beady', 'beaky', 'beamy', 'beaut', 'beefy', 'beery', 'beige', 'bendy', 'bifid', 'bijou', 'biped', 'birch', 'bitty', 'blame', 'bland', 'blank', 'blear', 'blest', 'blind', 'blond', 'blown', 'blowy', 'bluer', 'bluff', 'blunt', 'boned', 'bonny', 'boozy', 'bored', 'boric', 'bosky', 'bosom', 'bound', 'bovid', 'bowed', 'boxed', 'braky', 'brash', 'brief', 'briny', 'brisk', 'broad', 'broch', 'brood', 'brown', 'brute', 'buggy', 'bulgy', 'bumpy', 'burly', 'burnt', 'burry', 'bushy', 'busty', 'butch', 'buxom', 'cadgy', 'cagey', 'calmy', 'campy', 'canny', 'caped', 'cased', 'catty', 'cauld', 'cedar', 'cered', 'ceric', 'chary', 'cheap', 'cheek', 'chewy', 'chief', 'chill', 'chirk', 'choky', 'cissy', 'civil', 'cleft', 'coaly', 'color', 'comfy', 'comic', 'compo', 'conic', 'couth', 'coxal', 'crack', 'crank', 'crash', 'crass', 'crisp', 'cronk', 'cross', 'crude', 'cruel', 'crumb', 'cured', 'curly', 'curst', 'cushy', 'cutty', 'cynic', 'dated', 'dazed', 'dedal', 'deism', 'diazo', 'dicey', 'dingy', 'direr', 'dirty', 'dishy', 'dizzy', 'dolce', 'doped', 'dopey', 'dormy', 'dorty', 'dosed', 'dotal', 'dotty', 'dowdy', 'dowie', 'downy', 'dozen', 'drawn', 'dread', 'drear', 'dress', 'dried', 'ducky', 'duddy', 'dummy', 'dumpy', 'duple', 'dural', 'dusky', 'dusty', 'dutch', 'dying', 'eager', 'eaten', 'ebony', 'edged', 'eerie', 'eight', 'elder', 'elect', 'elfin', 'elite', 'empty', 'enate', 'enemy', 'epoxy', 'erect', 'ethic', 'every', 'extra', 'faced', 'faery', 'faint', 'famed', 'fancy', 'farci', 'fatal', 'fated', 'fatty', 'fazed', 'felon', 'fenny', 'ferny', 'fetal', 'fetid', 'fewer', 'fiery', 'fifty', 'filar', 'filmy', 'final', 'fined', 'finer', 'finny', 'fired', 'first', 'fishy', 'fixed', 'fizzy', 'flaky', 'flamy', 'flash', 'flawy', 'fleet', 'flory', 'flown', 'fluid', 'fluky', 'flush', 'focal', 'foggy', 'folio', 'forky', 'forte', 'forty', 'found', 'frail', 'frank', 'freed', 'freer', 'fresh', 'fried', 'front', 'frore', 'fuggy', 'funky', 'funny', 'furry', 'fusil', 'fussy', 'fuzzy', 'gabby', 'gamer', 'gamey', 'gamic', 'gammy', 'garni', 'gauge', 'gaunt', 'gauzy', 'gawky', 'gawsy', 'gemmy', 'genal', 'genic', 'ghast', 'gimpy', 'girly', 'glare', 'glary', 'glial', 'glued', 'gluey', 'godly', 'gooey', 'goofy', 'goosy', 'gouty', 'grade', 'grand', 'grapy', 'grave', 'gross', 'group', 'gruff', 'guest', 'gules', 'gulfy', 'gummy', 'gushy', 'gusty', 'gutsy', 'gutta', 'gypsy', 'gyral', 'hadal', 'hammy', 'handy', 'hardy', 'hasty', 'hated', 'hazel', 'heady', 'heapy', 'hefty', 'heigh', 'hempy', 'herby', 'hexed', 'hi-fi', 'hilly', 'hired', 'holey', 'honey', 'hooly', 'hoven', 'huger', 'hulky', 'humid', 'hunky', 'hyoid', 'idled', 'iliac', 'inane', 'incog', 'inert', 'inner', 'inter', 'iodic', 'ionic', 'irate', 'irony', 'itchy', 'jaggy', 'jammy', 'japan', 'jazzy', 'jerky', 'jetty', 'joint', 'jowly', 'juicy', 'jumpy', 'jural', 'kacha', 'kaput', 'kempt', 'keyed', 'kinky', 'known', 'kooky', 'kraal', 'laced', 'laigh', 'lairy', 'lamer', 'lardy', 'larky', 'lated', 'later', 'lathy', 'leady', 'leafy', 'leaky', 'leary', 'least', 'ledgy', 'leery', 'legal', 'leggy', 'lento', 'level', 'licht', 'licit', 'liege', 'light', 'liked', 'liney', 'lippy', 'lived', 'livid', 'loamy', 'loath', 'lobar', 'local', 'loony', 'loose', 'loral', 'losel', 'lousy', 'loved', 'lower', 'lowly', 'lowse', 'loyal', 'lucid', 'lucky', 'lumpy', 'lunar', 'lurid', 'lushy', 'lying', 'lyric', 'macho', 'macro', 'magic', 'major', 'malar', 'mangy', 'manky', 'manly', 'mardy', 'massy', 'mated', 'matte', 'mauve', 'mazed', 'mealy', 'meaty', 'medal', 'melic', 'mesic', 'mesne', 'messy', 'metal', 'miffy', 'milky', 'mined', 'minim', 'minor', 'minus', 'mired', 'mirky', 'misty', 'mixed', 'modal', 'model', 'moire', 'molar', 'moldy', 'moody', 'moony', 'mopey', 'moral', 'mossy', 'mothy', 'motor', 'mousy', 'moved', 'mucid', 'mucky', 'muddy', 'muggy', 'muley', 'mural', 'murky', 'mushy', 'muted', 'muzzy', 'myoid', 'naggy', 'naive', 'naked', 'named', 'nasty', 'natal', 'naval', 'nervy', 'newsy', 'nicer', 'niffy', 'nifty', 'ninth', 'nitty', 'nival', 'noble', 'nodal', 'noisy', 'non-U', 'north', 'nosed', 'noted', 'nowed', 'nubby', 'oaken', 'oared', 'oaten', 'obese', 'ocher', 'ochre', 'often', 'ohmic', 'oiled', 'olden', 'older', 'oleic', 'olive', 'optic', 'ortho', 'osmic', 'other', 'outer', 'ovoid', 'owing', 'owned', 'paced', 'pagan', 'paled', 'paler', 'pally', 'paper', 'pappy', 'parky', 'party', 'pasty', 'pavid', 'pawky', 'peaky', 'pearl', 'peart', 'peaty', 'pedal', 'peppy', 'perdu', 'perky', 'pesky', 'phony', 'piano', 'picky', 'piled', 'piney', 'pious', 'pique', 'pithy', 'platy', 'plump', 'plush', 'podgy', 'potty', 'power', 'prest', 'pricy', 'prima', 'prime', 'print', 'privy', 'prize', 'prone', 'proof', 'prosy', 'proud', 'proxy', 'pseud', 'pucka', 'pudgy', 'puffy', 'pukka', 'pupal', 'purer', 'pursy', 'pushy', 'pyoid', 'quack', 'quare', 'quasi', 'quiet', 'quits', 'rabic', 'rabid', 'radio', 'raked', 'randy', 'rapid', 'rarer', 'raspy', 'rathe', 'ratty', 'ready', 'reedy', 'reeky', 'refer', 'regal', 'riant', 'ridgy', 'right', 'riled', 'rimed', 'rindy', 'risen', 'risky', 'ritzy', 'rival', 'riven', 'robed', 'rocky', 'roily', 'roman', 'rooky', 'ropey', 'round', 'rowdy', 'ruddy', 'ruled', 'rummy', 'runic', 'runny', 'runty', 'rural', 'rusty', 'rutty', 'sable', 'salic', 'sandy', 'sappy', 'sarky', 'sassy', 'sated', 'saved', 'savvy', 'scald', 'scaly', 'scary', 'score', 'scrap', 'sedgy', 'seely', 'seral', 'sewed', 'shaky', 'sharp', 'sheen', 'shier', 'shill', 'shoal', 'shock', 'shoed', 'shore', 'short', 'shyer', 'silky', 'silly', 'silty', 'sixth', 'sixty', 'skint', 'slack', 'slant', 'sleek', 'slier', 'slimy', 'slung', 'small', 'smart', 'smoky', 'snaky', 'sneak', 'snide', 'snowy', 'snuff', 'so-so', 'soapy', 'sober', 'socko', 'solar', 'soled', 'solid', 'sonic', 'sooth', 'sooty', 'soppy', 'sorer', 'sound', 'soupy', 'spent', 'spicy', 'spiky', 'spiny', 'spiry', 'splay', 'split', 'sport', 'spumy', 'squat', 'staid', 'stiff', 'still', 'stoic', 'stone', 'stony', 'store', 'stout', 'straw', 'stray', 'strip', 'stung', 'suave', 'sudsy', 'sulfa', 'sulky', 'sunny', 'super', 'sural', 'surer', 'surfy', 'surgy', 'surly', 'swell', 'swept', 'swish', 'sworn', 'tabby', 'taboo', 'tacit', 'tacky', 'tamed', 'tamer', 'tangy', 'taped', 'tarot', 'tarry', 'tasty', 'tatty', 'taunt', 'tawie', 'teary', 'techy', 'telic', 'tenor', 'tense', 'tenth', 'tenty', 'tepid', 'terse', 'testy', 'third', 'tidal', 'tight', 'tiled', 'timid', 'tinct', 'tined', 'tippy', 'tipsy', 'tonal', 'toned', 'tonic', 'toric', 'total', 'tough', 'toxic', 'trade', 'treed', 'treen', 'trial', 'truer', 'tubal', 'tubby', 'tumid', 'tuned', 'tutti', 'twill', 'typal', 'typed', 'typic', 'umber', 'unapt', 'unbid', 'uncut', 'undue', 'undug', 'unfed', 'unfit', 'union', 'unlet', 'unmet', 'unwed', 'unwet', 'upper', 'upset', 'urban', 'utile', 'uveal', 'vagal', 'valid', 'vapid', 'varus', 'vatic', 'veiny', 'vital', 'vivid', 'vocal', 'vogie', 'volar', 'vying', 'wacky', 'wally', 'waney', 'warty', 'washy', 'waspy', 'waste', 'waugh', 'waxen', 'webby', 'wedgy', 'weeny', 'weepy', 'weest', 'weird', 'welsh', 'wersh', 'whist', 'white', 'whity', 'whole', 'wider', 'wight', 'winey', 'wired', 'wised', 'wiser', 'withy', 'wonky', 'woods', 'woozy', 'world', 'wormy', 'worse', 'worst', 'woven', 'wrath', 'wrier', 'wrong', 'wroth', 'xeric', 'yarer', 'yolky', 'young', 'yucky', 'yummy', 'zesty', 'zingy', 'zinky', 'zippy', 'zonal') # noqa: E501, Q000 # fmt: on while True: adjective = random.choice(ADJECTIVES) # nosec B311 diff --git a/dvc/repo/imp_url.py b/dvc/repo/imp_url.py index e02e40094..798919b81 100644 --- a/dvc/repo/imp_url.py +++ b/dvc/repo/imp_url.py @@ -1,5 +1,4 @@ import os -from typing import TYPE_CHECKING from dvc.repo.scm_context import scm_context from dvc.utils import relpath, resolve_output, resolve_paths @@ -8,9 +7,6 @@ from dvc.utils.fs import path_isin from ..exceptions import InvalidArgumentError, OutputDuplicationError from . import locked -if TYPE_CHECKING: - from dvc.dvcfile import DVCFile - @locked @scm_context @@ -26,14 +22,14 @@ def imp_url( remote=None, to_remote=False, desc=None, - type=None, # pylint: disable=redefined-builtin + type=None, # noqa: A002, pylint: disable=redefined-builtin labels=None, meta=None, jobs=None, fs_config=None, version_aware: bool = False, ): - from dvc.dvcfile import Dvcfile + from dvc.dvcfile import make_dvcfile from dvc.stage import Stage, create_stage, restore_fields out = resolve_output(url, out) @@ -78,14 +74,16 @@ def imp_url( out_obj = stage.outs[0] out_obj.annot.update(desc=desc, type=type, labels=labels, meta=meta) - dvcfile: "DVCFile" = Dvcfile(self, stage.path) # type: ignore + dvcfile = make_dvcfile(self, stage.path) dvcfile.remove() try: new_index = self.index.add(stage) new_index.check_graph() except OutputDuplicationError as exc: - raise OutputDuplicationError(exc.output, set(exc.stages) - {stage}) + raise OutputDuplicationError( # noqa: B904 + exc.output, set(exc.stages) - {stage} + ) if no_exec: stage.ignore_outs() diff --git a/dvc/repo/init.py b/dvc/repo/init.py index cb7bc624a..8d6c7c78e 100644 --- a/dvc/repo/init.py +++ b/dvc/repo/init.py @@ -43,7 +43,7 @@ def init(root_dir=os.curdir, no_scm=False, force=False, subdir=False): try: scm = SCM(root_dir, search_parent_directories=subdir, no_scm=no_scm) except SCMError: - raise InitError( + raise InitError( # noqa: B904 f"{root_dir} is not tracked by any supported SCM tool (e.g. Git). " "Use `--no-scm` if you don't want to use any SCM or " "`--subdir` if initializing inside a subdirectory of a parent SCM " diff --git a/dvc/repo/install.py b/dvc/repo/install.py index 1a673cc21..617309e1d 100644 --- a/dvc/repo/install.py +++ b/dvc/repo/install.py @@ -54,7 +54,9 @@ def install_hooks(scm: "Git") -> None: scm.verify_hook(hook) except GitHookAlreadyExists as exc: link = format_link("https://man.dvc.org/install") - raise DvcException(f"{exc}. Please refer to {link} for more info.") + raise DvcException( # noqa: B904 + f"{exc}. Please refer to {link} for more info." + ) for hook in hooks: scm.install_hook(hook, f"exec dvc git-hook {hook} $@") diff --git a/dvc/repo/ls.py b/dvc/repo/ls.py index 598fa4e44..5adc72c8c 100644 --- a/dvc/repo/ls.py +++ b/dvc/repo/ls.py @@ -61,7 +61,7 @@ def _ls( try: fs_path = fs.info(fs_path)["name"] except FileNotFoundError: - raise PathMissingError(path, repo, dvc_only=dvc_only) + raise PathMissingError(path, repo, dvc_only=dvc_only) # noqa: B904 infos = {} for root, dirs, files in fs.walk( diff --git a/dvc/repo/remove.py b/dvc/repo/remove.py index 1fded46b7..5d8635ed0 100644 --- a/dvc/repo/remove.py +++ b/dvc/repo/remove.py @@ -26,8 +26,7 @@ def remove(self: "Repo", target: str, outs: bool = False): # give a more helpful error message. if self.fs.exists(target + DVC_FILE_SUFFIX): raise StageFileIsNotDvcFileError(target) from e - else: - raise + raise for stage in stages: stage.remove(remove_outs=outs, force=outs) diff --git a/dvc/repo/reproduce.py b/dvc/repo/reproduce.py index 59e4a70bb..12c9be154 100644 --- a/dvc/repo/reproduce.py +++ b/dvc/repo/reproduce.py @@ -222,7 +222,7 @@ def _reproduce_stages( ) break - except Exception as exc: + except Exception as exc: # noqa: BLE001 raise ReproductionError(stage.addressing) from exc if on_unchanged is not None: diff --git a/dvc/repo/scm_context.py b/dvc/repo/scm_context.py index 83f623f28..3dbf02f1d 100644 --- a/dvc/repo/scm_context.py +++ b/dvc/repo/scm_context.py @@ -78,7 +78,7 @@ class SCMContext: try: gitignore_file = self.scm.ignore(path) except FileNotInRepoError as exc: - raise SCMError(str(exc)) + raise SCMError(str(exc)) # noqa: B904 if gitignore_file: logger.debug("Added '%s' to gitignore file.", path) @@ -94,7 +94,7 @@ class SCMContext: try: gitignore_file = self.scm.ignore_remove(path) except FileNotInRepoError as exc: - raise SCMError(str(exc)) + raise SCMError(str(exc)) # noqa: B904 if gitignore_file: return self.track_file(relpath(gitignore_file)) @@ -130,9 +130,7 @@ class SCMContext: and logger.isEnabledFor(logging.INFO) ): add_cmd = self._make_git_add_cmd(self.files_to_track) - logger.info( - f"\nTo track the changes with git, run:\n" f"\n{add_cmd}" - ) + logger.info(f"\nTo track the changes with git, run:\n\n{add_cmd}") logger.info( "\nTo enable auto staging, run:\n\n" "\tdvc config core.autostage true" diff --git a/dvc/repo/stage.py b/dvc/repo/stage.py index 74c71e1cd..61fde9c59 100644 --- a/dvc/repo/stage.py +++ b/dvc/repo/stage.py @@ -289,7 +289,7 @@ class StageLoad: path = self._get_filepath(path, name) dvcfile = Dvcfile(self.repo, path) # `dvcfile.stages` is not cached - stages = dvcfile.stages # type: ignore + stages = dvcfile.stages # type: ignore[attr-defined] if isinstance(stages, SingleStageLoader): stage = stages[name] @@ -311,7 +311,7 @@ class StageLoad: path = self._get_filepath(path, name) dvcfile = Dvcfile(self.repo, path) - stages = dvcfile.stages # type: ignore + stages = dvcfile.stages # type: ignore[attr-defined] return stages[name] @@ -517,7 +517,7 @@ class StageLoad: path = self._get_filepath(path) dvcfile = Dvcfile(self.repo, path) - stages = dvcfile.stages # type: ignore + stages = dvcfile.stages if isinstance(stages, SingleStageLoader): stages_ = [stages[None]] diff --git a/dvc/repo/update.py b/dvc/repo/update.py index 142942a1f..e088da9a1 100644 --- a/dvc/repo/update.py +++ b/dvc/repo/update.py @@ -34,11 +34,14 @@ def update( "--to-remote can't be used with --no-download" ) - if not to_remote and remote: - if not self.cloud.get_remote(name=remote).worktree: - raise InvalidArgumentError( - "--remote can't be used without --to-remote" - ) + if ( + not to_remote + and remote + and not self.cloud.get_remote(name=remote).worktree + ): + raise InvalidArgumentError( + "--remote can't be used without --to-remote" + ) import_stages = set() other_stage_infos: List["StageInfo"] = [] diff --git a/dvc/repo/worktree.py b/dvc/repo/worktree.py index 16ed24f0e..d964b2718 100644 --- a/dvc/repo/worktree.py +++ b/dvc/repo/worktree.py @@ -232,17 +232,20 @@ def update_worktree_stages( ) continue entry = remote_index[key] - if entry.meta and entry.meta.isdir: - if not any( + if ( + entry.meta + and entry.meta.isdir + and not any( subkey != key and subentry.meta and not subentry.meta.isdir for subkey, subentry in remote_index.iteritems(key) - ): - logger.warning( - "Could not update '%s', directory is empty in the " - "remote", - out, - ) - continue + ) + ): + logger.warning( + "Could not update '%s', directory is empty in the " + "remote", + out, + ) + continue _fetch_out_changes(out, local_index, remote_index, remote) stage.save(merge_versioned=True) for out in stage.outs: diff --git a/dvc/schema.py b/dvc/schema.py index 02c16c276..d80c2a0f4 100644 --- a/dvc/schema.py +++ b/dvc/schema.py @@ -46,7 +46,7 @@ LOCKFILE_V2_SCHEMA = { OUT_PSTAGE_DETAILED_SCHEMA = { str: { - **ANNOTATION_SCHEMA, # type: ignore + **ANNOTATION_SCHEMA, # type: ignore[arg-type] Output.PARAM_CACHE: bool, Output.PARAM_PERSIST: bool, Output.PARAM_CHECKPOINT: bool, diff --git a/dvc/scm.py b/dvc/scm.py index c61efa907..0d32c1055 100644 --- a/dvc/scm.py +++ b/dvc/scm.py @@ -169,11 +169,11 @@ def resolve_rev(scm: "Git", rev: str) -> str: try: ref_infos = resolve_name(scm, rev).get(rev) except AmbiguousExpRefInfo: - raise RevError(f"ambiguous Git revision '{rev}'") + raise RevError(f"ambiguous Git revision '{rev}'") # noqa: B904 if ref_infos: return scm.get_ref(str(ref_infos)) - raise RevError(str(exc)) + raise RevError(str(exc)) # noqa: B904 def _get_n_commits(scm: "Git", revs: List[str], num: int) -> List[str]: diff --git a/dvc/stage/__init__.py b/dvc/stage/__init__.py index 40c0d07b7..5b8443a9f 100644 --- a/dvc/stage/__init__.py +++ b/dvc/stage/__init__.py @@ -276,7 +276,7 @@ class Stage(params.StageParams): with suppress(ValueError): # try to use first non-empty line as a description line = next(filter(None, self.desc.splitlines())) - desc = line.strip() + return line.strip() return desc def _read_env(self, out, checkpoint_func=None) -> Env: diff --git a/dvc/stage/loader.py b/dvc/stage/loader.py index 9f4412a05..7e17055da 100644 --- a/dvc/stage/loader.py +++ b/dvc/stage/loader.py @@ -126,7 +126,7 @@ class StageLoader(Mapping): try: resolved_data = self.resolver.resolve_one(name) except EntryNotFound: - raise StageNotFound(self.dvcfile, name) + raise StageNotFound(self.dvcfile, name) # noqa: B904 if self.lockfile_data and name not in self.lockfile_data: self.lockfile_needs_update() diff --git a/dvc/stage/monitor.py b/dvc/stage/monitor.py index b0869ba2b..8dc49c167 100644 --- a/dvc/stage/monitor.py +++ b/dvc/stage/monitor.py @@ -110,7 +110,7 @@ class Monitor: try: task.execute() task.updated.set() - except Exception: # pylint: disable=broad-except + except Exception: # noqa: BLE001, pylint: disable=W0703 logger.exception( "Error running '%s' task, '%s' will be aborted", task.name, diff --git a/dvc/stage/run.py b/dvc/stage/run.py index 0b5f5379d..b87f31303 100644 --- a/dvc/stage/run.py +++ b/dvc/stage/run.py @@ -66,7 +66,7 @@ def prepare_kwargs(stage, checkpoint_func=None, run_env=None): # #L1426 # [2] https://github.com/iterative/dvc/issues/2506 # #issuecomment-535396799 - kwargs["shell"] = True if os.name == "nt" else False + kwargs["shell"] = os.name == "nt" return kwargs diff --git a/dvc/stage/serialize.py b/dvc/stage/serialize.py index 5a5e57a59..308dc7bee 100644 --- a/dvc/stage/serialize.py +++ b/dvc/stage/serialize.py @@ -201,8 +201,9 @@ def to_single_stage_file(stage: "Stage", **kwargs): # - apply changes to a returned structure # - serialize it text = stage._stage_text # noqa, pylint: disable=protected-access - if text is not None: - saved_state = parse_yaml_for_update(text, stage.path) - apply_diff(state, saved_state) - state = saved_state - return state + if text is None: + return state + + saved_state = parse_yaml_for_update(text, stage.path) + apply_diff(state, saved_state) + return saved_state diff --git a/dvc/stage/utils.py b/dvc/stage/utils.py index bb1442375..d534bb1e4 100644 --- a/dvc/stage/utils.py +++ b/dvc/stage/utils.py @@ -273,7 +273,7 @@ def check_stage_exists( raise StageFileAlreadyExistsError( f"'{stage.relpath}' already exists. {hint}" ) - elif stage.name and stage.name in dvcfile.stages: + if stage.name and stage.name in dvcfile.stages: raise DuplicateStageName( f"Stage '{stage.name}' already exists in '{stage.relpath}'. {hint}" ) diff --git a/dvc/testing/fixtures.py b/dvc/testing/fixtures.py index ffae60ee3..ee6b00344 100644 --- a/dvc/testing/fixtures.py +++ b/dvc/testing/fixtures.py @@ -1,5 +1,6 @@ import os import subprocess +from typing import Dict, Tuple import pytest @@ -24,7 +25,7 @@ __all__ = [ "docker_services", ] -CACHE = {} # type: ignore +CACHE: Dict[Tuple[bool, bool, bool], str] = {} @pytest.fixture(scope="session") @@ -42,9 +43,9 @@ def make_tmp_dir(tmp_path_factory, request, worker_id): cache = CACHE.get((scm, dvc, subdir)) if not cache: - cache = tmp_path_factory.mktemp("dvc-test-cache" + worker_id) - TmpDir(cache).init(scm=scm, dvc=dvc, subdir=subdir) - CACHE[(scm, dvc, subdir)] = os.fspath(cache) + cache_dir = tmp_path_factory.mktemp("dvc-test-cache" + worker_id) + TmpDir(cache_dir).init(scm=scm, dvc=dvc, subdir=subdir) + CACHE[(scm, dvc, subdir)] = cache = os.fspath(cache_dir) path = tmp_path_factory.mktemp(name) if isinstance(name, str) else name # ignore sqlite files from .dvc/tmp. We might not be closing the cache @@ -111,7 +112,7 @@ def local_cloud(make_cloud): @pytest.fixture -def make_remote(tmp_dir, dvc, make_cloud): +def make_remote(tmp_dir, dvc, make_cloud): # noqa: ARG001 def _make_remote(name, typ="local", **kwargs): cloud = make_cloud(typ) # pylint: disable=W0621 tmp_dir.add_remote(name=name, config=cloud.config, **kwargs) @@ -179,7 +180,7 @@ def docker(): @pytest.fixture(scope="session") -def docker_compose(docker): +def docker_compose(docker): # noqa: ARG001 try: subprocess.check_output("docker-compose version", shell=True) except (subprocess.CalledProcessError, OSError): diff --git a/dvc/ui/__init__.py b/dvc/ui/__init__.py index c0466329f..92aa3c45b 100644 --- a/dvc/ui/__init__.py +++ b/dvc/ui/__init__.py @@ -279,7 +279,7 @@ class Console: if not opened: ui.error_write( - f"Failed to open {url}. " "Please try opening it manually." + f"Failed to open {url}. Please try opening it manually." ) return 1 diff --git a/dvc/ui/pager.py b/dvc/ui/pager.py index 2fe53703f..9f8fa2538 100644 --- a/dvc/ui/pager.py +++ b/dvc/ui/pager.py @@ -73,7 +73,7 @@ def find_pager(): # `-R` is required to render ansi escape sequences for exp show # and, `-S` is required for horizontal scrolling. less_env = bool(os.getenv(LESS)) - pager = prepare_default_pager( + return prepare_default_pager( ansi_escapes=True, chop_long_lines=True, quit_if_one_screen=not less_env, diff --git a/dvc/updater.py b/dvc/updater.py index 2d8ad8428..07c4026d7 100644 --- a/dvc/updater.py +++ b/dvc/updater.py @@ -77,9 +77,9 @@ class Updater: try: info = json.load(fobj) latest = info["version"] - except Exception as exc: # pylint: disable=broad-except + except Exception as e: # noqa: BLE001 # pylint: disable=W0703 logger.debug( - "'%s' is not a valid json: %s", self.updater_file, exc + "'%s' is not a valid json: %s", self.updater_file, e ) self.fetch() return diff --git a/dvc/utils/__init__.py b/dvc/utils/__init__.py index e9c1dfa1d..dadd57497 100644 --- a/dvc/utils/__init__.py +++ b/dvc/utils/__init__.py @@ -196,14 +196,12 @@ def boxify(message, border_color=None): for line in lines ] - box_str = "{margin}{padding}{content}{padding}{margin}".format( + return "{margin}{padding}{content}{padding}{margin}".format( margin=colorize(margin, color=border_color), padding="".join(padding_lines), content="".join(content_lines), ) - return box_str - def _visual_width(line): """Get the the number of columns required to display a string""" @@ -410,7 +408,7 @@ def error_handler(func): vals = func(*args, **kwargs) if vals: result["data"] = vals - except Exception as e: # pylint: disable=broad-except + except Exception as e: # noqa: BLE001, pylint: disable=broad-except if onerror is not None: onerror(result, e, **kwargs) return result diff --git a/dvc/utils/collections.py b/dvc/utils/collections.py index 8b36e92e5..dd04202d7 100644 --- a/dvc/utils/collections.py +++ b/dvc/utils/collections.py @@ -58,9 +58,9 @@ def to_omegaconf(item): Cast the custom classes to Python primitives. """ if isinstance(item, dict): - item = {k: to_omegaconf(v) for k, v in item.items()} - elif isinstance(item, list): - item = [to_omegaconf(x) for x in item] + return {k: to_omegaconf(v) for k, v in item.items()} + if isinstance(item, list): + return [to_omegaconf(x) for x in item] return item @@ -177,7 +177,6 @@ def nested_contains(dictionary: Dict, phrase: str) -> bool: if key == phrase and val: return True - if isinstance(val, dict): - if nested_contains(val, phrase): - return True + if isinstance(val, dict) and nested_contains(val, phrase): + return True return False diff --git a/dvc/utils/fs.py b/dvc/utils/fs.py index 0fcb44b39..1376dd065 100644 --- a/dvc/utils/fs.py +++ b/dvc/utils/fs.py @@ -40,7 +40,7 @@ def contains_symlink_up_to(path: "StrPath", base_path: "StrPath"): return contains_symlink_up_to(os.path.dirname(path), base_path) -def _chmod(func, p, excinfo): # pylint: disable=unused-argument +def _chmod(func, p, excinfo): # noqa: ARG001, pylint: disable=unused-argument perm = os.lstat(p).st_mode perm |= stat.S_IWRITE diff --git a/dvc/utils/serialize/_common.py b/dvc/utils/serialize/_common.py index 243016ef1..5fab60943 100644 --- a/dvc/utils/serialize/_common.py +++ b/dvc/utils/serialize/_common.py @@ -65,7 +65,7 @@ class EncodingError(ParseError): def _load_data(path: "AnyPath", parser: ParserFn, fs: "FileSystem" = None): open_fn = fs.open if fs else open encoding = "utf-8" - with open_fn(path, encoding=encoding) as fd: # type: ignore + with open_fn(path, encoding=encoding) as fd: with reraise(UnicodeDecodeError, EncodingError(path, encoding)): return parser(fd.read(), path) @@ -78,7 +78,7 @@ def _dump_data( **dumper_args, ): open_fn = fs.open if fs else open - with open_fn(path, "w+", encoding="utf-8") as fd: # type: ignore + with open_fn(path, "w+", encoding="utf-8") as fd: dumper(data, fd, **dumper_args) @@ -90,7 +90,7 @@ def _modify_data( fs: "FileSystem" = None, ): exists_fn = fs.exists if fs else os.path.exists - file_exists = exists_fn(path) # type: ignore + file_exists = exists_fn(path) data = _load_data(path, parser=parser, fs=fs) if file_exists else {} yield data dumper(path, data, fs=fs) diff --git a/dvc/utils/serialize/_py.py b/dvc/utils/serialize/_py.py index c0e49a6c5..ea6d9f1ca 100644 --- a/dvc/utils/serialize/_py.py +++ b/dvc/utils/serialize/_py.py @@ -23,8 +23,7 @@ def parse_py(text, path): with reraise(SyntaxError, PythonFileCorruptedError(path)): tree = ast.parse(text, filename=path) - result = _ast_tree_to_dict(tree) - return result + return _ast_tree_to_dict(tree) def parse_py_for_update(text, path): @@ -67,7 +66,7 @@ def _dump(data, stream): try: ast.parse(new_text) except SyntaxError: - raise PythonFileCorruptedError( + raise PythonFileCorruptedError( # noqa: B904 stream.name, "Python file structure is corrupted after update params", ) @@ -161,9 +160,7 @@ def _ast_assign_to_dict(assign, only_self_params=False, lineno=False): def _get_ast_name(target, only_self_params=False): if hasattr(target, "id") and not only_self_params: - result = target.id - elif hasattr(target, "attr") and target.value.id == "self": - result = target.attr - else: - raise AttributeError - return result + return target.id + if hasattr(target, "attr") and target.value.id == "self": + return target.attr + raise AttributeError diff --git a/dvc/utils/serialize/_yaml.py b/dvc/utils/serialize/_yaml.py index 12e0b70a4..896d5984a 100644 --- a/dvc/utils/serialize/_yaml.py +++ b/dvc/utils/serialize/_yaml.py @@ -26,7 +26,7 @@ def parse_yaml(text, path, typ="safe"): yaml = YAML(typ=typ) with reraise(_YAMLError, YAMLFileCorruptedError(path)): - return yaml.load(text) or {} + return yaml.load(text) or {} # noqa: S506 def parse_yaml_for_update(text, path): diff --git a/dvc/utils/strictyaml.py b/dvc/utils/strictyaml.py index c511cb25c..59d1e1152 100644 --- a/dvc/utils/strictyaml.py +++ b/dvc/utils/strictyaml.py @@ -286,7 +286,7 @@ def load( rev = getattr(fs, "rev", None) try: - with open_fn(path, encoding=encoding) as fd: # type: ignore + with open_fn(path, encoding=encoding) as fd: text = fd.read() data = parse_yaml(text, path, typ="rt" if round_trip else "safe") except UnicodeDecodeError as exc: diff --git a/pyproject.toml b/pyproject.toml index f29f91fbb..930b8faa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,6 +159,28 @@ profile = "black" known_first_party = ["dvc", "dvc_data", "dvc_objects", "dvc_render", "dvc_task", "tests"] line_length = 79 + + +[tool.ruff] +line-length = 79 +ignore = ["S101", "RUF100", "SIM105", "SIM108", "SIM110", "SIM111", "SIM117", "A003", "ARG002"] +select = ["F", "E", "W", "C90", "UP", "YTT", "S", "BLE", "B", "A", "C4", "T10", "ISC", "ICN", "T20", "PT003", "PT008", "PT009", "PT010", "PT015", "PT016", "PT017", "PT020", "PT021", "PT024", "PT025", "PT026", "Q", "RET501", "RET504", "SIM", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "PIE", "RUF"] +target-version = "py38" + +[tool.ruff.per-file-ignores] +"scripts/**/*.py" = ["T201"] + +[tool.ruff.mccabe] +max-complexity = 15 + +[tool.ruff.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false +parametrize-names-type = "csv" + +[tool.ruff.flake8-unused-arguments] +ignore-variadic-names = true + [tool.pytest.ini_options] log_level = "debug" addopts = "-ra --cov-config pyproject.toml" diff --git a/scripts/build.py b/scripts/build.py index a6b1240f6..37302bc31 100644 --- a/scripts/build.py +++ b/scripts/build.py @@ -1,5 +1,6 @@ import argparse import pathlib +import sys from subprocess import STDOUT, check_call path = pathlib.Path(__file__).parent.absolute() @@ -37,7 +38,7 @@ if args.sign_application: raise NotImplementedError if not args.application_id: print("--sign-application requires --application-id") - exit(1) + sys.exit(1) check_call( ["python", "sign.py", "--application-id", args.application_id], cwd=pyinstaller, @@ -64,7 +65,7 @@ if args.sign_installer: [args.installer_id, args.apple_id_username, args.apple_id_password] ): print("--sign-installer requires --installer-id") - exit(1) + sys.exit(1) check_call( ["python", "sign.py", "--installer-id", args.installer_id], cwd=fpm, @@ -78,7 +79,7 @@ if args.notarize: print( "--notarize requires --apple-id-username and --apple-id-password" ) - exit(1) + sys.exit(1) check_call( [ "python", diff --git a/scripts/fpm/notarize.py b/scripts/fpm/notarize.py index 06c4c4c7c..bca3e4514 100644 --- a/scripts/fpm/notarize.py +++ b/scripts/fpm/notarize.py @@ -36,11 +36,11 @@ else: pkgs = list(path.glob("*.pkg")) if not pkgs: print("No pkgs found") - exit(1) + sys.exit(1) if len(pkgs) > 1: print("Too many packages") - exit(1) + sys.exit(1) (pkg,) = pkgs diff --git a/scripts/fpm/sign.py b/scripts/fpm/sign.py index 02f69df1a..697f9275e 100644 --- a/scripts/fpm/sign.py +++ b/scripts/fpm/sign.py @@ -28,11 +28,11 @@ else: pkgs = list(path.glob("*.pkg")) if not pkgs: print("No pkgs found") - exit(1) + sys.exit(1) if len(pkgs) > 1: print("Too many packages") - exit(1) + sys.exit(1) (pkg,) = pkgs diff --git a/tests/func/experiments/conftest.py b/tests/func/experiments/conftest.py index 5ece8fd2d..9bc19b89c 100644 --- a/tests/func/experiments/conftest.py +++ b/tests/func/experiments/conftest.py @@ -24,7 +24,7 @@ def http_auth_patch(mocker): @pytest.fixture(params=[True, False]) -def workspace(request, test_queue) -> bool: # noqa +def workspace(request, test_queue) -> bool: # noqa: F811 return request.param diff --git a/tests/func/experiments/test_show.py b/tests/func/experiments/test_show.py index 973144766..1ba3655e0 100644 --- a/tests/func/experiments/test_show.py +++ b/tests/func/experiments/test_show.py @@ -998,8 +998,7 @@ def test_show_queued_error(tmp_dir, scm, dvc, exp_stage, mocker): def resolve_commit(rev): if rev == exp_rev_3: raise SCMError - else: - return commit_2 + return commit_2 mocker.patch.object( scm, @@ -1027,8 +1026,7 @@ def test_show_completed_error(tmp_dir, scm, dvc, exp_stage, mocker): def resolve_commit(rev): if rev == exp_rev_3: raise SCMError - else: - return commit_2 + return commit_2 mocker.patch.object( scm, @@ -1088,8 +1086,7 @@ def test_show_baseline_error(tmp_dir, scm, dvc, exp_stage, mocker): def resolve_commit(rev): if rev == baseline_rev: raise SCMError - else: - return commit_2 + return commit_2 mocker.patch.object( scm, diff --git a/tests/func/test_add.py b/tests/func/test_add.py index d52e7bb02..c13514da9 100644 --- a/tests/func/test_add.py +++ b/tests/func/test_add.py @@ -400,7 +400,7 @@ def test_should_update_state_entry_for_directory_after_add( assert ret == 0 assert file_md5_counter.mock.call_count == 8 - os.rename("data", "data" + ".back") + os.rename("data", "data.back") ret = main(["checkout"]) assert ret == 0 assert file_md5_counter.mock.call_count == 8 @@ -417,7 +417,7 @@ def test_add_commit(tmp_dir, dvc): assert os.path.isfile("foo") assert not os.path.exists(dvc.odb.local.path) - ret = main(["commit", "foo" + ".dvc"]) + ret = main(["commit", "foo.dvc"]) assert ret == 0 assert os.path.isfile("foo") assert dvc.odb.local.exists("acbd18db4cc2f85cedef654fccc4a4d8") diff --git a/tests/func/test_checkout.py b/tests/func/test_checkout.py index b226fd678..598a271a1 100644 --- a/tests/func/test_checkout.py +++ b/tests/func/test_checkout.py @@ -12,7 +12,6 @@ from dvc.exceptions import ( CheckoutError, CheckoutErrorSuggestGit, ConfirmRemoveError, - DvcException, NoOutputOrStageError, ) from dvc.fs import LocalFileSystem, system @@ -310,27 +309,20 @@ def test_checkout_hook(mocker, tmp_dir, dvc): def test_checkout_suggest_git(tmp_dir, dvc, scm): # pylint: disable=no-member - tmp_dir.dvc_gen("foo", "foo") - try: + with pytest.raises(CheckoutErrorSuggestGit) as e: dvc.checkout(targets="gitbranch") - except DvcException as exc: - assert isinstance(exc, CheckoutErrorSuggestGit) - assert isinstance(exc.__cause__, NoOutputOrStageError) - assert isinstance(exc.__cause__.__cause__, StageFileDoesNotExistError) + assert isinstance(e.value.__cause__, NoOutputOrStageError) + assert isinstance(e.value.__cause__.__cause__, StageFileDoesNotExistError) - try: - dvc.checkout(targets="foo") - except DvcException as exc: - assert isinstance(exc, CheckoutErrorSuggestGit) - assert isinstance(exc.__cause__, NoOutputOrStageError) - assert exc.__cause__.__cause__ is None + with pytest.raises(CheckoutErrorSuggestGit) as e: + dvc.checkout(targets="foobar") + assert isinstance(e.value.__cause__, NoOutputOrStageError) + assert isinstance(e.value.__cause__.__cause__, StageFileDoesNotExistError) - try: + with pytest.raises(CheckoutErrorSuggestGit) as e: dvc.checkout(targets="looks-like-dvcfile.dvc") - except DvcException as exc: - assert isinstance(exc, CheckoutErrorSuggestGit) - assert isinstance(exc.__cause__, StageFileDoesNotExistError) - assert exc.__cause__.__cause__ is None + assert isinstance(e.value.__cause__, StageFileDoesNotExistError) + assert e.value.__cause__.__cause__ is None def test_checkout_target_recursive_should_not_remove_other_used_files( @@ -347,7 +339,7 @@ def test_checkout_recursive_not_directory(tmp_dir, dvc): ret = main(["add", "foo"]) assert ret == 0 - stats = dvc.checkout(targets=["foo" + ".dvc"], recursive=True) + stats = dvc.checkout(targets=["foo.dvc"], recursive=True) assert stats == {"added": [], "modified": [], "deleted": []} @@ -603,7 +595,7 @@ def test_stats_does_not_show_changes_by_default(tmp_dir, dvc, scm, capsys): assert main(["checkout", "--summary"]) == 0 out, _ = capsys.readouterr() - assert "2 files deleted" == out.rstrip() + assert out.rstrip() == "2 files deleted" @pytest.mark.parametrize("link", ["hardlink", "symlink", "copy"]) diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py index 2350ac7eb..dc1871d7f 100644 --- a/tests/func/test_data_cloud.py +++ b/tests/func/test_data_cloud.py @@ -312,7 +312,6 @@ def clean(outs, dvc=None): if dvc: dvc.odb.local.clear() for path in outs: - print(path) remove(path) if dvc: clean_repos() diff --git a/tests/func/test_ignore.py b/tests/func/test_ignore.py index 46e433631..6d5c1f879 100644 --- a/tests/func/test_ignore.py +++ b/tests/func/test_ignore.py @@ -264,7 +264,7 @@ def test_ignore_blank_line(tmp_dir, dvc): # It is not possible to re-include a file if a parent directory of # that file is excluded. -# Git doesn’t list excluded directories for performance reasons, +# Git doesn't list excluded directories for performance reasons, # so any patterns on contained files have no effect, # no matter where they are defined. @pytest.mark.parametrize( diff --git a/tests/func/test_init.py b/tests/func/test_init.py index ee7f235e6..2649fa6ab 100644 --- a/tests/func/test_init.py +++ b/tests/func/test_init.py @@ -65,7 +65,7 @@ def test_init_quiet_should_not_display_welcome_screen(tmp_dir, scm, caplog): ret = main(["init", "--quiet"]) assert ret == 0 - assert "" == caplog.text + assert caplog.text == "" def test_allow_init_dvc_subdir(tmp_dir, scm, monkeypatch): diff --git a/tests/func/test_move.py b/tests/func/test_move.py index feffa6362..acd554a2a 100644 --- a/tests/func/test_move.py +++ b/tests/func/test_move.py @@ -29,7 +29,7 @@ def test_move_directory(tmp_dir, dvc): def test_cmd_move(tmp_dir, dvc): tmp_dir.dvc_gen("foo", "foo") - assert main(["move", "foo", "foo" + "1"]) == 0 + assert main(["move", "foo", "foo1"]) == 0 assert main(["move", "non-existing-file", "dst"]) != 0 @@ -188,7 +188,6 @@ def test_move_meta(tmp_dir, dvc): dvc.move("foo", "bar") res = (tmp_dir / "bar.dvc").read_text() - print(res) assert res == textwrap.dedent( """\ outs: diff --git a/tests/func/test_odb.py b/tests/func/test_odb.py index 4d858cfe4..16dfb0a5d 100644 --- a/tests/func/test_odb.py +++ b/tests/func/test_odb.py @@ -187,7 +187,7 @@ def test_shared_cache(tmp_dir, dvc, group): assert not os.path.exists(cache_dir) tmp_dir.dvc_gen( - {"file": "file content", "dir": {"file2": "file 2 " "content"}} + {"file": "file content", "dir": {"file2": "file 2 content"}} ) file_mode = oct(0o444) diff --git a/tests/func/test_run_multistage.py b/tests/func/test_run_multistage.py index f83b7e18b..b0c52e6c0 100644 --- a/tests/func/test_run_multistage.py +++ b/tests/func/test_run_multistage.py @@ -106,7 +106,7 @@ def test_multistage_with_wdir(tmp_dir, dvc): ) data, _ = Dvcfile(dvc, stage.path)._load() - assert "dir" == data["stages"]["copy-foo1-foo2"]["wdir"] + assert data["stages"]["copy-foo1-foo2"]["wdir"] == "dir" def test_multistage_always_changed(tmp_dir, dvc): @@ -305,7 +305,7 @@ def test_run_params_no_exec(tmp_dir, dvc): def test_run_without_cmd(tmp_dir, dvc, kwargs): with pytest.raises(InvalidArgumentError) as exc: dvc.run(**kwargs) - assert "command is not specified" == str(exc.value) + assert str(exc.value) == "command is not specified" def test_run_overwrite_order(tmp_dir, dvc, run_copy): diff --git a/tests/func/test_update.py b/tests/func/test_update.py index 9d7a35154..ab25d3363 100644 --- a/tests/func/test_update.py +++ b/tests/func/test_update.py @@ -283,7 +283,7 @@ def test_update_rev(tmp_dir, dvc, scm, git_dir): "rev_lock": branch1_head, } with open(tmp_dir / "foo", encoding="utf-8") as f: - assert "foobar" == f.read() + assert f.read() == "foobar" stage = dvc.update(["foo.dvc"], rev="branch2")[0] assert stage.deps[0].def_repo == { @@ -292,7 +292,7 @@ def test_update_rev(tmp_dir, dvc, scm, git_dir): "rev_lock": branch2_head, } with open(tmp_dir / "foo", encoding="utf-8") as f: - assert "foobar foo" == f.read() + assert f.read() == "foobar foo" def test_update_recursive(tmp_dir, dvc, erepo_dir): diff --git a/tests/pylint_plugin_disable.py b/tests/pylint_plugin_disable.py index 684e572e3..ab50080ca 100644 --- a/tests/pylint_plugin_disable.py +++ b/tests/pylint_plugin_disable.py @@ -12,7 +12,7 @@ from pylint.checkers.base import NameChecker from pylint.checkers.classes import ClassChecker if TYPE_CHECKING: - from astroid import node_classes, scoped_nodes # noqa + from astroid import node_classes, scoped_nodes from pylint.lint import PyLinter @@ -34,14 +34,12 @@ def is_node_in_tests(node: "node_classes.NodeNG"): return module.file.startswith(TESTS_FOLDER) -def register(linter: "PyLinter"): # noqa +def register(linter: "PyLinter"): try: from pylint_plugin_utils import suppress_message except ImportError: - print("Cannot suppress message. 'pylint_plugin_utils' not installed.") return - print("Registered custom plugin. Some checks will be disabled for tests.") for msg, checks in SUPPRESS_CHECKS.items(): for checker_method in checks: suppress_message(linter, checker_method, msg, is_node_in_tests) diff --git a/tests/ruff.toml b/tests/ruff.toml new file mode 100644 index 000000000..484e03f57 --- /dev/null +++ b/tests/ruff.toml @@ -0,0 +1,3 @@ +extend = "../pyproject.toml" +# keep in sync with `../pyproject.toml` +ignore = ["S", "RUF001", "RUF100", "SIM105", "SIM108", "SIM110", "SIM111", "SIM117", "A003", "ARG001", "ARG002"] diff --git a/tests/unit/command/test_machine.py b/tests/unit/command/test_machine.py index e4d4bcdce..6db8fb258 100644 --- a/tests/unit/command/test_machine.py +++ b/tests/unit/command/test_machine.py @@ -174,5 +174,4 @@ def test_help_message(tmp_dir, scm, dvc, cmd, use_config, capsys): except SystemExit: pass cap = capsys.readouterr() - print(cap.out) assert ("--global" in cap.out) is use_config diff --git a/tests/unit/output/test_local.py b/tests/unit/output/test_local.py index b9c24d4aa..d2f041c2f 100644 --- a/tests/unit/output/test_local.py +++ b/tests/unit/output/test_local.py @@ -18,7 +18,7 @@ def test_str_workdir_inside_repo(dvc): stage = Stage(dvc) output = Output(stage, "path", cache=False) - assert "path" == str(output) + assert str(output) == "path" stage = Stage(dvc, wdir="some_folder") output = Output(stage, "path", cache=False) diff --git a/tests/unit/proc/test_manager.py b/tests/unit/proc/test_manager.py index bfd6f7146..6ec9f3bf9 100644 --- a/tests/unit/proc/test_manager.py +++ b/tests/unit/proc/test_manager.py @@ -63,8 +63,7 @@ def test_dead_process(tmp_dir, mocker, running_process): err = OSError() err.winerror = 87 raise err - else: - raise ProcessLookupError() + raise ProcessLookupError() mocker.patch("os.kill", side_effect=side_effect) with pytest.raises(ProcessLookupError): diff --git a/tests/unit/stage/test_stage.py b/tests/unit/stage/test_stage.py index e27c4a335..51c923a08 100644 --- a/tests/unit/stage/test_stage.py +++ b/tests/unit/stage/test_stage.py @@ -78,7 +78,7 @@ def test_stage_update(dvc, mocker): @pytest.mark.skipif( not isinstance( threading.current_thread(), - threading._MainThread, # noqa, pylint: disable=protected-access + threading._MainThread, # pylint: disable=protected-access ), reason="Not running in the main thread.", ) diff --git a/tests/unit/test_daemon.py b/tests/unit/test_daemon.py index 4e4d0592d..cff3c00c5 100644 --- a/tests/unit/test_daemon.py +++ b/tests/unit/test_daemon.py @@ -19,7 +19,7 @@ def test_daemon(mocker): args = mock_posix.call_args[0] env = args[1] - assert "PYTHONPATH" in env.keys() + assert "PYTHONPATH" in env file_path = os.path.abspath(inspect.stack()[0][1]) file_dir = os.path.dirname(file_path) diff --git a/tests/unit/test_ignore.py b/tests/unit/test_ignore.py index c5f4b5a3d..e1eea525b 100644 --- a/tests/unit/test_ignore.py +++ b/tests/unit/test_ignore.py @@ -13,11 +13,7 @@ def mock_dvcignore(dvcignore_path, patterns): fs.path = localfs.path fs.sep = localfs.sep with patch.object(fs, "open", mock_open(read_data="\n".join(patterns))): - ignore_patterns = DvcIgnorePatterns.from_file( - dvcignore_path, fs, "mocked" - ) - - return ignore_patterns + return DvcIgnorePatterns.from_file(dvcignore_path, fs, "mocked") @pytest.mark.parametrize( @@ -45,7 +41,7 @@ def mock_dvcignore(dvcignore_path, patterns): ("to_ignore.txt", ["!to_ignore.txt", "to_ignore*"], True), # It is not possible to re-include a file if a parent directory of # that file is excluded. - # Git doesn’t list excluded directories for performance reasons, + # Git doesn't list excluded directories for performance reasons, # so any patterns on contained files have no effect, # no matter where they are defined. # see (`tests/func/test_ignore.py::test_ignore_parent_path`) diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index 364b69b59..20bae7bd2 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -44,7 +44,7 @@ class TestColorFormatter: with caplog.at_level(logging.INFO, logger="dvc"): logger.info("message") - assert "message" == formatter.format(caplog.records[0]) + assert formatter.format(caplog.records[0]) == "message" def test_warning(self, caplog): with caplog.at_level(logging.INFO, logger="dvc"): @@ -66,7 +66,7 @@ class TestColorFormatter: with caplog.at_level(logging.INFO, logger="dvc"): try: raise ValueError - except Exception: + except Exception: # noqa: BLE001 logger.exception("message") expected = "{red}ERROR{nc}: message".format(**colors) @@ -77,7 +77,7 @@ class TestColorFormatter: with caplog.at_level(logging.INFO, logger="dvc"): try: raise Exception("description") - except Exception: + except Exception: # noqa: BLE001 logger.exception("") expected = "{red}ERROR{nc}: description".format(**colors) @@ -88,7 +88,7 @@ class TestColorFormatter: with caplog.at_level(logging.INFO, logger="dvc"): try: raise Exception("description") - except Exception: + except Exception: # noqa: BLE001 logger.exception("message") expected = "{red}ERROR{nc}: message - description".format(**colors) @@ -101,7 +101,7 @@ class TestColorFormatter: with caplog.at_level(logging.DEBUG, logger="dvc"): try: raise Exception("description") - except Exception: + except Exception: # noqa: BLE001 stack_trace = traceback.format_exc() logger.exception("") @@ -124,7 +124,7 @@ class TestColorFormatter: with caplog.at_level(logging.DEBUG, logger="dvc"): try: raise Exception("description") - except Exception: + except Exception: # noqa: BLE001 stack_trace = traceback.format_exc() logger.debug("", exc_info=True) @@ -147,7 +147,7 @@ class TestColorFormatter: with caplog.at_level(logging.DEBUG, logger="dvc"): try: raise Exception("description") - except Exception: + except Exception: # noqa: BLE001 stack_trace = traceback.format_exc() logger.exception("something", extra={"tb_only": True}) @@ -170,7 +170,7 @@ class TestColorFormatter: with caplog.at_level(logging.DEBUG, logger="dvc"): try: raise Exception("first") - except Exception as exc: + except Exception as exc: # noqa: BLE001 try: raise DvcException("second") from exc except DvcException: @@ -251,7 +251,7 @@ def test_info_with_debug_loglevel_shows_no_datetime(caplog, dt): with caplog.at_level(logging.DEBUG, logger="dvc"): logger.info("message") - assert "message" == formatter.format(caplog.records[0]) + assert formatter.format(caplog.records[0]) == "message" def test_add_existing_level(caplog, dt):