Repository: iterative/dvc
Pull request #9788 (2023-08-01 23:08:06)

ls/import/get: introduce --remote and --remote-config
Similar to how it is used in api. `--remote` is used to specify a new default remote to set, while `--remote-config` can amend that (or just old default if `--remote` not specified). Example:

```
dvc get URL path --remote-config "access_key_id=mykey" "secret_access_key=mysecretkey"
```

Fixes https://github.com/iterative/dvc/issues/9656

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) Report
Patch coverage: **`56.25%`** and project coverage change: **`-0.40%`** :warning:
> Comparison is base [(`5c2fd67`)](https://app.codecov.io/gh/iterative/dvc/commit/5c2fd67e6d4b2d0472c1d70893e65a001be57992?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) 90.46% compared to head [(`3061e36`)](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) 90.07%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #9788      +/-   ##
==========================================
- Coverage   90.46%   90.07%   -0.40%     
==========================================
  Files         480      480              
  Lines       36615    36644      +29     
  Branches     5277     3405    -1872     
==========================================
- Hits        33125    33007     -118     
- Misses       2892     2998     +106     
- Partials      598      639      +41     
```


| [Files Changed](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) | Coverage Δ | |
|---|---|---|
| [dvc/repo/ls.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vbHMucHk=) | `94.87% <ø> (ø)` | |
| [tests/unit/command/test\_get.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-dGVzdHMvdW5pdC9jb21tYW5kL3Rlc3RfZ2V0LnB5) | `100.00% <ø> (ø)` | |
| [tests/unit/command/test\_imp.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-dGVzdHMvdW5pdC9jb21tYW5kL3Rlc3RfaW1wLnB5) | `100.00% <ø> (ø)` | |
| [dvc/repo/imp.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vaW1wLnB5) | `26.31% <7.69%> (-45.12%)` | :arrow_down: |
| [dvc/dependency/repo.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2RlcGVuZGVuY3kvcmVwby5weQ==) | `87.01% <54.54%> (-5.53%)` | :arrow_down: |
| [dvc/cli/utils.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2NsaS91dGlscy5weQ==) | `76.00% <69.23%> (-7.34%)` | :arrow_down: |
| [dvc/commands/get.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2NvbW1hbmRzL2dldC5weQ==) | `86.36% <100.00%> (+0.64%)` | :arrow_up: |
| [dvc/commands/imp.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2NvbW1hbmRzL2ltcC5weQ==) | `82.35% <100.00%> (+1.10%)` | :arrow_up: |
| [dvc/commands/ls/\_\_init\_\_.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2NvbW1hbmRzL2xzL19faW5pdF9fLnB5) | `90.69% <100.00%> (+0.45%)` | :arrow_up: |
| [dvc/repo/get.py](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vZ2V0LnB5) | `92.59% <100.00%> (ø)` | |
| ... and [1 more](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) | |

... and [52 files with indirect coverage changes](https://app.codecov.io/gh/iterative/dvc/pull/9788/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/iterative/dvc/pull/9788?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative).


Patch:
diff --git a/.flake8 b/.flake8
index fd87e09b7..736ce6620 100644
--- a/.flake8
+++ b/.flake8
@@ -1,19 +1,20 @@
 [flake8]
-ignore=
+max_line_length = 88
+extend-ignore=
     # Whitespace before ':'
     E203
-    # Too many leading '#' for block comment
-    E266
     # Line break occurred before a binary operator
     W503
     # unindexed parameters in the str.format, see:
     # https://pypi.org/project/flake8-string-format/
     P1
-    # use `!r` instead of surrounding by quotes
-    B028
-max_line_length = 88
 max-complexity = 10
-select = B,C,E,F,W,T4,B902,T,P,TYP
 show_source = true
 count = true
 min_python_version = 3.8.0
+per-file-ignores =
+    dvc/testing/benchmarks/conftest.py:F401
+    dvc/testing/conftest.py:F401
+    dvc/testing/plugin.py:F401
+    tests/conftest.py:F401
+    tests/func/experiments/executor/test_ssh.py:F401
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bd0518508..362bc4824 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -23,17 +23,6 @@ repos:
       - id: mixed-line-ending
       - id: sort-simple-yaml
       - id: trailing-whitespace
-  - repo: https://github.com/lovesegfault/beautysh
-    rev: v6.2.1
-    hooks:
-      - id: beautysh
-        args: ["-i", "2"]
-  - repo: https://github.com/asottile/pyupgrade
-    rev: v3.9.0
-    hooks:
-      - id: pyupgrade
-        args:
-          - --py38-plus
   - repo: https://github.com/psf/black
     rev: 23.7.0
     hooks:
@@ -43,7 +32,7 @@ repos:
     hooks:
       - id: isort
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: 'v0.0.278'
+    rev: 'v0.0.282'
     hooks:
       - id: ruff
   - repo: https://github.com/codespell-project/codespell
@@ -52,13 +41,10 @@ repos:
       - id: codespell
         additional_dependencies: ["tomli"]
   - repo: https://github.com/PyCQA/flake8
-    rev: 6.0.0
+    rev: 6.1.0
     hooks:
       - id: flake8
         additional_dependencies:
-          - flake8-bugbear
-          - flake8-comprehensions
-          - flake8-debugger
           - flake8-string-format
           - flake8-typing-imports
   - repo: https://github.com/PyCQA/bandit
diff --git a/dvc/api/data.py b/dvc/api/data.py
index a1f9e78c3..d9951b4ce 100644
--- a/dvc/api/data.py
+++ b/dvc/api/data.py
@@ -67,7 +67,7 @@ class _OpenContextManager(GCM):
         raise AttributeError("dvc.api.open() should be used in a with statement.")
 
 
-def open(  # noqa, pylint: disable=redefined-builtin
+def open(  # noqa: A001, pylint: disable=redefined-builtin
     path: str,
     repo: Optional[str] = None,
     rev: Optional[str] = None,
diff --git a/dvc/cli/__init__.py b/dvc/cli/__init__.py
index 74e457dc6..10ee56902 100644
--- a/dvc/cli/__init__.py
+++ b/dvc/cli/__init__.py
@@ -230,7 +230,7 @@ def main(argv=None):  # noqa: C901, PLR0912, PLR0915
         logger.exception("")
     except DvcParserError:
         ret = 254
-    except Exception as exc:  # noqa, pylint: disable=broad-except
+    except Exception as exc:  # noqa: BLE001, pylint: disable=broad-except
         # pylint: disable=no-member
         ret = _log_exceptions(exc) or 255
 
diff --git a/dvc/cli/utils.py b/dvc/cli/utils.py
index 6b794d7b2..1dfd83536 100644
--- a/dvc/cli/utils.py
+++ b/dvc/cli/utils.py
@@ -1,3 +1,27 @@
+import argparse
+
+
+class DictAction(argparse.Action):
+    def __call__(self, parser, args, values, option_string=None):  # noqa: ARG002
+        d = getattr(args, self.dest) or {}
+
+        if isinstance(values, list):
+            kvs = values
+        else:
+            kvs = [values]
+
+        for kv in kvs:
+            key, value = kv.split("=")
+            if not value:
+                raise argparse.ArgumentError(
+                    self,
+                    f'Could not parse argument "{values}" as k1=v1 k2=v2 ... format',
+                )
+            d[key] = value
+
+        setattr(args, self.dest, d)
+
+
 def fix_subparsers(subparsers):
     """Workaround for bug in Python 3. See more info at:
     https://bugs.python.org/issue16308
diff --git a/dvc/commands/get.py b/dvc/commands/get.py
index 016a23415..c1c4ad2d6 100644
--- a/dvc/commands/get.py
+++ b/dvc/commands/get.py
@@ -3,7 +3,7 @@ import logging
 
 from dvc.cli import completion
 from dvc.cli.command import CmdBaseNoRepo
-from dvc.cli.utils import append_doc_link
+from dvc.cli.utils import DictAction, append_doc_link
 from dvc.exceptions import DvcException
 
 logger = logging.getLogger(__name__)
@@ -38,6 +38,8 @@ class CmdGet(CmdBaseNoRepo):
                 jobs=self.args.jobs,
                 force=self.args.force,
                 config=self.args.config,
+                remote=self.args.remote,
+                remote_config=self.args.remote_config,
             )
             return 0
         except CloneError:
@@ -111,4 +113,19 @@ def add_parser(subparsers, parent_parser):
             "in the target repository."
         ),
     )
+    get_parser.add_argument(
+        "--remote",
+        type=str,
+        help="Remote name to set as a default in the target repository.",
+    )
+    get_parser.add_argument(
+        "--remote-config",
+        type=str,
+        nargs="*",
+        action=DictAction,
+        help=(
+            "Remote config options to merge with a remote's config (default or one "
+            "specified by '--remote') in the target repository."
+        ),
+    )
     get_parser.set_defaults(func=CmdGet)
diff --git a/dvc/commands/imp.py b/dvc/commands/imp.py
index 217df87eb..51874a76b 100644
--- a/dvc/commands/imp.py
+++ b/dvc/commands/imp.py
@@ -3,7 +3,7 @@ import logging
 
 from dvc.cli import completion
 from dvc.cli.command import CmdBase
-from dvc.cli.utils import append_doc_link
+from dvc.cli.utils import DictAction, append_doc_link
 from dvc.exceptions import DvcException
 
 logger = logging.getLogger(__name__)
@@ -23,6 +23,8 @@ class CmdImport(CmdBase):
                 no_download=self.args.no_download,
                 jobs=self.args.jobs,
                 config=self.args.config,
+                remote=self.args.remote,
+                remote_config=self.args.remote_config,
             )
         except CloneError:
             logger.exception("failed to import '%s'", self.args.path)
@@ -103,4 +105,19 @@ def add_parser(subparsers, parent_parser):
             "in the target repository."
         ),
     )
+    import_parser.add_argument(
+        "--remote",
+        type=str,
+        help="Remote name to set as a default in the target repository.",
+    )
+    import_parser.add_argument(
+        "--remote-config",
+        type=str,
+        nargs="*",
+        action=DictAction,
+        help=(
+            "Remote config options to merge with a remote's config (default or one "
+            "specified by '--remote') in the target repository."
+        ),
+    )
     import_parser.set_defaults(func=CmdImport)
diff --git a/dvc/commands/ls/__init__.py b/dvc/commands/ls/__init__.py
index ecb345064..c044e3571 100644
--- a/dvc/commands/ls/__init__.py
+++ b/dvc/commands/ls/__init__.py
@@ -3,7 +3,7 @@ import logging
 
 from dvc.cli import completion
 from dvc.cli.command import CmdBaseNoRepo
-from dvc.cli.utils import append_doc_link
+from dvc.cli.utils import DictAction, append_doc_link
 from dvc.commands.ls.ls_colors import LsColors
 from dvc.exceptions import DvcException
 from dvc.ui import ui
@@ -35,6 +35,8 @@ class CmdList(CmdBaseNoRepo):
                 recursive=self.args.recursive,
                 dvc_only=self.args.dvc_only,
                 config=self.args.config,
+                remote=self.args.remote,
+                remote_config=self.args.remote_config,
             )
             if self.args.json:
                 ui.write_json(entries)
@@ -89,6 +91,21 @@ def add_parser(subparsers, parent_parser):
             "in the target repository."
         ),
     )
+    list_parser.add_argument(
+        "--remote",
+        type=str,
+        help="Remote name to set as a default in the target repository.",
+    )
+    list_parser.add_argument(
+        "--remote-config",
+        type=str,
+        nargs="*",
+        action=DictAction,
+        help=(
+            "Remote config options to merge with a remote's config (default or one "
+            "specified by '--remote') in the target repository."
+        ),
+    )
     list_parser.add_argument(
         "path",
         nargs="?",
diff --git a/dvc/compare.py b/dvc/compare.py
index cceb9d7bd..ad57c3268 100644
--- a/dvc/compare.py
+++ b/dvc/compare.py
@@ -57,7 +57,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()}  # noqa: SIM118
+        projection = {k: self.column(k) for k in self.keys()}
         return projection.items()
 
     def keys(self) -> List[str]:
@@ -171,8 +171,7 @@ 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()  # noqa: SIM118
+            with_value(d.get(key), self._fill_value) for key in self.keys()
         ]
         self.append(row)
 
@@ -227,7 +226,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
             to_drop -= match_line
 
         if axis == "rows":
-            for name in self.keys():  # noqa: SIM118
+            for name in self.keys():
                 self._columns[name] = Column(
                     [x for n, x in enumerate(self._columns[name]) if n not in to_drop]
                 )
@@ -275,7 +274,7 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
                 else:
                     unique_rows.append(tuple_row)
 
-            for name in self.keys():  # noqa: SIM118
+            for name in self.keys():
                 self._columns[name] = Column(
                     [
                         x
diff --git a/dvc/dependency/repo.py b/dvc/dependency/repo.py
index 7c6fbb43b..dfe6a8b72 100644
--- a/dvc/dependency/repo.py
+++ b/dvc/dependency/repo.py
@@ -1,6 +1,6 @@
 from typing import TYPE_CHECKING, Dict, Optional, Union
 
-from voluptuous import Required
+from voluptuous import Any, Required
 
 from dvc.utils import as_posix
 
@@ -17,6 +17,7 @@ class RepoDependency(Dependency):
     PARAM_REV = "rev"
     PARAM_REV_LOCK = "rev_lock"
     PARAM_CONFIG = "config"
+    PARAM_REMOTE = "remote"
 
     REPO_SCHEMA = {
         PARAM_REPO: {
@@ -24,6 +25,7 @@ class RepoDependency(Dependency):
             PARAM_REV: str,
             PARAM_REV_LOCK: str,
             PARAM_CONFIG: str,
+            PARAM_REMOTE: Any(str, dict),
         }
     }
 
@@ -76,6 +78,10 @@ class RepoDependency(Dependency):
         if config:
             repo[self.PARAM_CONFIG] = config
 
+        remote = self.def_repo.get(self.PARAM_REMOTE)
+        if remote:
+            repo[self.PARAM_REMOTE] = remote
+
         return {
             self.PARAM_PATH: self.def_path,
             self.PARAM_REPO: repo,
@@ -99,6 +105,14 @@ class RepoDependency(Dependency):
         from dvc.config import Config
         from dvc.fs import DVCFileSystem
 
+        rem = self.def_repo.get("remote")
+        if isinstance(rem, dict):
+            remote = None  # type: ignore[unreachable]
+            remote_config = rem
+        else:
+            remote = rem
+            remote_config = None
+
         conf = self.def_repo.get("config")
         if conf:
             config = Config.load_file(conf)
@@ -113,6 +127,8 @@ class RepoDependency(Dependency):
             rev=rev or self._get_rev(locked=locked),
             subrepos=True,
             config=config,
+            remote=remote,
+            remote_config=remote_config,
         )
 
     def _get_rev(self, locked: bool = True):
diff --git a/dvc/exceptions.py b/dvc/exceptions.py
index 5c6b2844d..fd0a0b26e 100644
--- a/dvc/exceptions.py
+++ b/dvc/exceptions.py
@@ -297,7 +297,7 @@ class URLMissingError(DvcException):
         super().__init__(f"The path '{url}' does not exist")
 
 
-class IsADirectoryError(DvcException):  # noqa,pylint:disable=redefined-builtin
+class IsADirectoryError(DvcException):  # noqa: A001,pylint:disable=redefined-builtin
     """Raised when a file operation is requested on a directory."""
 
 
diff --git a/dvc/logger.py b/dvc/logger.py
index 2ab0b9311..b49468840 100644
--- a/dvc/logger.py
+++ b/dvc/logger.py
@@ -160,7 +160,7 @@ class LoggerHandler(logging.StreamHandler):
             self.flush()
         except (BrokenPipeError, RecursionError):
             raise
-        except Exception:  # noqa, pylint: disable=broad-except
+        except Exception:  # noqa: BLE001, pylint: disable=broad-except
             self.handleError(record)
 
 
diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py
index 69475d8b9..33c14b80a 100644
--- a/dvc/repo/__init__.py
+++ b/dvc/repo/__init__.py
@@ -497,7 +497,7 @@ class Repo:
         used_run_cache=None,
         revs=None,
         num=1,
-        push: bool = False,  # noqa: F811
+        push: bool = False,
     ):
         """Get the stages related to the given target and collect
         the `info` of its outputs.
diff --git a/dvc/repo/get.py b/dvc/repo/get.py
index 239a08318..4617e0cd3 100644
--- a/dvc/repo/get.py
+++ b/dvc/repo/get.py
@@ -19,7 +19,17 @@ class GetDVCFileError(DvcException):
         )
 
 
-def get(url, path, out=None, rev=None, jobs=None, force=False, config=None):
+def get(
+    url,
+    path,
+    out=None,
+    rev=None,
+    jobs=None,
+    force=False,
+    config=None,
+    remote=None,
+    remote_config=None,
+):
     from dvc.config import Config
     from dvc.dvcfile import is_valid_filename
     from dvc.repo import Repo
@@ -38,6 +48,8 @@ def get(url, path, out=None, rev=None, jobs=None, force=False, config=None):
         subrepos=True,
         uninitialized=True,
         config=config,
+        remote=remote,
+        remote_config=remote_config,
     ) as repo:
         from dvc.fs import download
         from dvc.fs.data import DataFileSystem
diff --git a/dvc/repo/imp.py b/dvc/repo/imp.py
index 558fa56da..8f4e2dcaf 100644
--- a/dvc/repo/imp.py
+++ b/dvc/repo/imp.py
@@ -1,4 +1,14 @@
-def imp(self, url, path, out=None, rev=None, config=None, **kwargs):
+def imp(
+    self,
+    url,
+    path,
+    out=None,
+    rev=None,
+    config=None,
+    remote=None,
+    remote_config=None,
+    **kwargs,
+):
     erepo = {"url": url}
     if rev is not None:
         erepo["rev"] = rev
@@ -6,4 +16,17 @@ def imp(self, url, path, out=None, rev=None, config=None, **kwargs):
     if config is not None:
         erepo["config"] = config
 
+    if remote is not None and remote_config is not None:
+        conf = erepo.get("config") or {}
+        remotes = conf.get("remote") or {}
+        remote_conf = remotes.get(remote) or {}
+        remote_conf.update(remote_config)
+        remotes[remote] = remote_conf
+        conf["remote"] = remotes
+        erepo["config"] = conf
+    elif remote is not None:
+        erepo["remote"] = remote
+    elif remote_config is not None:
+        erepo["remote"] = remote_config
+
     return self.imp_url(path, out=out, erepo=erepo, frozen=True, **kwargs)
diff --git a/dvc/repo/ls.py b/dvc/repo/ls.py
index ce408bbb5..5e6005ea3 100644
--- a/dvc/repo/ls.py
+++ b/dvc/repo/ls.py
@@ -14,6 +14,8 @@ def ls(
     recursive: Optional[bool] = None,
     dvc_only: bool = False,
     config: Optional[str] = None,
+    remote: Optional[str] = None,
+    remote_config: Optional[dict] = None,
 ):
     """Methods for getting files and outputs for the repo.
 
@@ -23,7 +25,9 @@ def ls(
         rev (str, optional): SHA commit, branch or tag name
         recursive (bool, optional): recursively walk the repo
         dvc_only (bool, optional): show only DVC-artifacts
-        config (bool, optional): path to config file
+        config (str, optional): path to config file
+        remote (str, optional): remote name to set as a default remote in the repo
+        remote_config (str, dict): remote config to merge with a remote in the repo
 
     Returns:
         list of `entry`
@@ -47,7 +51,13 @@ def ls(
         config_dict = None
 
     with Repo.open(
-        url, rev=rev, subrepos=True, uninitialized=True, config=config_dict
+        url,
+        rev=rev,
+        subrepos=True,
+        uninitialized=True,
+        config=config_dict,
+        remote=remote,
+        remote_config=remote_config,
     ) as repo:
         path = path or ""
 
diff --git a/dvc/repo/plots/__init__.py b/dvc/repo/plots/__init__.py
index 5d1053455..878df858a 100644
--- a/dvc/repo/plots/__init__.py
+++ b/dvc/repo/plots/__init__.py
@@ -483,7 +483,7 @@ def _collect_pipeline_files(repo, targets: List[str], props, onerror=None):
             if isinstance(elem, str):
                 dvcfile_defs_dict[elem] = None
             else:
-                k, v = list(elem.items())[0]
+                k, v = next(iter(elem.items()))
                 dvcfile_defs_dict[k] = v
 
         resolved = _resolve_definitions(
diff --git a/dvc/repo/worktree.py b/dvc/repo/worktree.py
index 6fd86874b..9cf39253b 100644
--- a/dvc/repo/worktree.py
+++ b/dvc/repo/worktree.py
@@ -427,7 +427,7 @@ def _get_update_diff_index(
         meta_cmp_key=partial(_meta_checksum, remote.fs),
         with_unchanged=True,
     ):
-        if change.typ == ADD or change.typ == MODIFY:
+        if change.typ in (ADD, MODIFY):
             entry = change.new
             # preserve md5's which were calculated in out.save() after
             # downloading
diff --git a/dvc/scm.py b/dvc/scm.py
index fd3239522..945fb33cf 100644
--- a/dvc/scm.py
+++ b/dvc/scm.py
@@ -67,7 +67,7 @@ def map_scm_exception(with_cause: bool = False) -> Iterator[None]:
         into = SCMError(str(exc))
         if with_cause:
             raise into from exc
-        raise into
+        raise into  # noqa: B904
 
 
 @overload
diff --git a/dvc/utils/serialize/__init__.py b/dvc/utils/serialize/__init__.py
index c5f7f9759..5afbab435 100644
--- a/dvc/utils/serialize/__init__.py
+++ b/dvc/utils/serialize/__init__.py
@@ -1,11 +1,11 @@
 from collections import defaultdict
 from typing import DefaultDict
 
-from ._common import *  # noqa, pylint: disable=wildcard-import
-from ._json import *  # noqa, pylint: disable=wildcard-import
-from ._py import *  # noqa, pylint: disable=wildcard-import
-from ._toml import *  # noqa, pylint: disable=wildcard-import
-from ._yaml import *  # noqa, pylint: disable=wildcard-import
+from ._common import *  # noqa: F403, pylint: disable=wildcard-import
+from ._json import *  # noqa: F403, pylint: disable=wildcard-import
+from ._py import *  # noqa: F403, pylint: disable=wildcard-import
+from ._toml import *  # noqa: F403, pylint: disable=wildcard-import
+from ._yaml import *  # noqa: F403, pylint: disable=wildcard-import
 
 LOADERS: DefaultDict[str, LoaderFn] = defaultdict(  # noqa: F405
     lambda: load_yaml  # noqa: F405
diff --git a/pyproject.toml b/pyproject.toml
index d065fb907..244dd4255 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -300,7 +300,7 @@ ignore-variadic-names = true
 known-first-party = ["dvc", "dvc_data", "dvc_objects", "dvc_render", "dvc_task", "tests"]
 
 [tool.ruff.pep8-naming]
-ignore-names = ["M", "SCM"]
+extend-ignore-names = ["M", "SCM"]
 
 [tool.ruff.per-file-ignores]
 "dvc/commands/**" = ["N806"]


Test patch:
diff --git a/dvc/testing/benchmarks/conftest.py b/dvc/testing/benchmarks/conftest.py
index 12c22cdf0..ac47567f7 100644
--- a/dvc/testing/benchmarks/conftest.py
+++ b/dvc/testing/benchmarks/conftest.py
@@ -1,1 +1,1 @@
-from .fixtures import *  # noqa, pylint: disable=wildcard-import
+from .fixtures import *  # noqa: F403 # pylint: disable=wildcard-import
diff --git a/dvc/testing/conftest.py b/dvc/testing/conftest.py
index 12c22cdf0..6e01dd914 100644
--- a/dvc/testing/conftest.py
+++ b/dvc/testing/conftest.py
@@ -1,1 +1,1 @@
-from .fixtures import *  # noqa, pylint: disable=wildcard-import
+from .fixtures import *  # noqa: F403, pylint: disable=wildcard-import
diff --git a/dvc/testing/plugin.py b/dvc/testing/plugin.py
index 435f9140c..54c9e468f 100644
--- a/dvc/testing/plugin.py
+++ b/dvc/testing/plugin.py
@@ -1,5 +1,5 @@
-from .benchmarks.fixtures import *  # noqa, pylint: disable=wildcard-import
-from .fixtures import *  # noqa, pylint: disable=wildcard-import
+from .benchmarks.fixtures import *  # noqa: F403, pylint: disable=wildcard-import
+from .fixtures import *  # noqa: F403, pylint: disable=wildcard-import
 
 
 def pytest_generate_tests(metafunc):
diff --git a/tests/conftest.py b/tests/conftest.py
index 19e530baf..1422860cd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,11 +7,11 @@ import pytest
 
 from dvc import env
 from dvc.stage import PipelineStage
-from dvc.testing.fixtures import *  # noqa, pylint: disable=wildcard-import
+from dvc.testing.fixtures import *  # noqa: F403, pylint: disable=wildcard-import
 
-from .dir_helpers import *  # noqa, pylint: disable=wildcard-import
-from .remotes import *  # noqa, pylint: disable=wildcard-import
-from .scripts import *  # noqa, pylint: disable=wildcard-import
+from .dir_helpers import *  # noqa: F403 # pylint: disable=wildcard-import
+from .remotes import *  # noqa: F403 # pylint: disable=wildcard-import
+from .scripts import *  # noqa: F403, pylint: disable=wildcard-import
 
 # Prevent updater and analytics from running their processes
 os.environ["DVC_TEST"] = "true"
diff --git a/tests/func/api/test_data.py b/tests/func/api/test_data.py
index 09267e8a8..348d7c598 100644
--- a/tests/func/api/test_data.py
+++ b/tests/func/api/test_data.py
@@ -5,7 +5,7 @@ from funcy import first, get_in
 
 from dvc import api
 from dvc.exceptions import OutputNotFoundError, PathMissingError
-from dvc.testing.api_tests import TestAPI  # noqa, pylint: disable=unused-import
+from dvc.testing.api_tests import TestAPI  # noqa: F401, pylint: disable=unused-import
 from dvc.testing.tmp_dir import make_subrepo
 from dvc.utils.fs import remove
 
diff --git a/tests/func/experiments/conftest.py b/tests/func/experiments/conftest.py
index df4cffb4d..ab478326e 100644
--- a/tests/func/experiments/conftest.py
+++ b/tests/func/experiments/conftest.py
@@ -1,6 +1,7 @@
 import pytest
 
-from tests.unit.repo.experiments.conftest import (  # noqa, pylint disable=unused-argument
+# pylint: disable-next=unused-argument
+from tests.unit.repo.experiments.conftest import (  # noqa: F401
     exp_stage,
     failed_exp_stage,
     session_app,
diff --git a/tests/func/experiments/executor/test_ssh.py b/tests/func/experiments/executor/test_ssh.py
index 7714d1912..7f4ecdd88 100644
--- a/tests/func/experiments/executor/test_ssh.py
+++ b/tests/func/experiments/executor/test_ssh.py
@@ -10,7 +10,9 @@ from dvc_ssh.tests.cloud import TEST_SSH_KEY_PATH, TEST_SSH_USER
 from dvc.repo.experiments.executor.base import ExecutorInfo, ExecutorResult
 from dvc.repo.experiments.executor.ssh import SSHExecutor
 from dvc.repo.experiments.refs import EXEC_HEAD, EXEC_MERGE
-from tests.func.machine.conftest import *  # noqa, pylint: disable=wildcard-import
+
+# pylint: disable-next=wildcard-import
+from tests.func.machine.conftest import *  # noqa: F403
 
 
 @contextmanager
diff --git a/tests/func/test_add.py b/tests/func/test_add.py
index 6154d0eee..e340e6422 100644
--- a/tests/func/test_add.py
+++ b/tests/func/test_add.py
@@ -452,9 +452,9 @@ def temporary_windows_drive(tmp_path_factory):
         if len(s) > 0
     ]
 
-    new_drive_name = [
+    new_drive_name = next(
         letter for letter in string.ascii_uppercase if letter not in drives
-    ][0]
+    )
     new_drive = f"{new_drive_name}:"
 
     target_path = tmp_path_factory.mktemp("tmp_windows_drive")
diff --git a/tests/func/test_config.py b/tests/func/test_config.py
index 5e6e297c9..9829b3ad1 100644
--- a/tests/func/test_config.py
+++ b/tests/func/test_config.py
@@ -218,7 +218,7 @@ def test_list_bad_args(tmp_dir, dvc, caplog, args):
 
 
 def test_set_invalid_key(dvc):
-    with pytest.raises(ConfigError, match=r"extra keys not allowed"):  # noqa: PT012
+    with pytest.raises(ConfigError, match=r"extra keys not allowed"):
         with dvc.config.edit() as conf:
             conf["core"]["invalid_key"] = "value"
 
@@ -227,9 +227,7 @@ def test_merging_two_levels(dvc):
     with dvc.config.edit() as conf:
         conf["remote"]["test"] = {"url": "ssh://example.com"}
 
-    with pytest.raises(  # noqa: PT012
-        ConfigError, match=r"expected 'url' for dictionary value"
-    ):
+    with pytest.raises(ConfigError, match=r"expected 'url' for dictionary value"):
         with dvc.config.edit("global") as conf:
             conf["remote"]["test"] = {"password": "1"}
 
diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py
index 000d6ab20..ebefe848a 100644
--- a/tests/func/test_data_cloud.py
+++ b/tests/func/test_data_cloud.py
@@ -10,7 +10,9 @@ from dvc.cli import main
 from dvc.exceptions import CheckoutError
 from dvc.repo.open_repo import clean_repos
 from dvc.stage.exceptions import StageNotFound
-from dvc.testing.remote_tests import TestRemote  # noqa, pylint: disable=unused-import
+from dvc.testing.remote_tests import (  # noqa: F401, pylint: disable=unused-import
+    TestRemote,
+)
 from dvc.utils.fs import remove
 from dvc_data.hashfile.db import HashFileDB
 from dvc_data.hashfile.db.local import LocalHashFileDB
diff --git a/tests/func/test_stage.py b/tests/func/test_stage.py
index 51814fcfc..c8beb7baf 100644
--- a/tests/func/test_stage.py
+++ b/tests/func/test_stage.py
@@ -84,7 +84,7 @@ def test_default_wdir_ignored_in_checksum(tmp_dir, dvc):
     stage = dvc.run(cmd="cp bar foo", deps=["bar"], outs=["foo"], name="copy-foo-bar")
 
     d = stage.dumpd()
-    assert Stage.PARAM_WDIR not in d.keys()
+    assert Stage.PARAM_WDIR not in d
 
     d = load_yaml("dvc.yaml")
     assert Stage.PARAM_WDIR not in d["stages"]["copy-foo-bar"]
@@ -246,13 +246,17 @@ def test_collect_symlink(tmp_dir, dvc, with_deps):
         with pytest.raises(StageNotFoundError):
             dvc.stage.collect(target=str(data_link / "foo.dvc"), with_deps=with_deps)
     else:
-        stage = list(
-            dvc.stage.collect(target=str(data_link / "foo.dvc"), with_deps=with_deps)
-        )[0]
+        stage = next(
+            iter(
+                dvc.stage.collect(
+                    target=str(data_link / "foo.dvc"), with_deps=with_deps
+                )
+            )
+        )
 
         assert stage.addressing == os.path.join("data_link", "foo.dvc")
 
-    stage = list(dvc.stage.collect(target=f"{foo_path}.dvc", with_deps=with_deps))[0]
+    stage = next(iter(dvc.stage.collect(target=f"{foo_path}.dvc", with_deps=with_deps)))
 
     assert stage.addressing == os.path.join("data", "foo.dvc")
 
diff --git a/tests/remotes/__init__.py b/tests/remotes/__init__.py
index 02c1b15ed..c95987efd 100644
--- a/tests/remotes/__init__.py
+++ b/tests/remotes/__init__.py
@@ -1,4 +1,4 @@
-from dvc_ssh.tests.fixtures import (  # noqa, pylint: disable=unused-import
+from dvc_ssh.tests.fixtures import (  # noqa: F401, pylint: disable=unused-import
     make_ssh,
     ssh,
     ssh_server,
diff --git a/tests/unit/command/ls/test_ls.py b/tests/unit/command/ls/test_ls.py
index 1d6702624..884dae137 100644
--- a/tests/unit/command/ls/test_ls.py
+++ b/tests/unit/command/ls/test_ls.py
@@ -19,7 +19,14 @@ def test_list(mocker):
     url = "local_dir"
     m = _test_cli(mocker, url)
     m.assert_called_once_with(
-        url, None, recursive=False, rev=None, dvc_only=False, config=None
+        url,
+        None,
+        recursive=False,
+        rev=None,
+        dvc_only=False,
+        config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
@@ -27,7 +34,14 @@ def test_list_recursive(mocker):
     url = "local_dir"
     m = _test_cli(mocker, url, "-R")
     m.assert_called_once_with(
-        url, None, recursive=True, rev=None, dvc_only=False, config=None
+        url,
+        None,
+        recursive=True,
+        rev=None,
+        dvc_only=False,
+        config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
@@ -35,7 +49,14 @@ def test_list_git_ssh_rev(mocker):
     url = "git@github.com:repo"
     m = _test_cli(mocker, url, "--rev", "123")
     m.assert_called_once_with(
-        url, None, recursive=False, rev="123", dvc_only=False, config=None
+        url,
+        None,
+        recursive=False,
+        rev="123",
+        dvc_only=False,
+        config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
@@ -44,7 +65,14 @@ def test_list_targets(mocker):
     target = "subdir"
     m = _test_cli(mocker, url, target)
     m.assert_called_once_with(
-        url, target, recursive=False, rev=None, dvc_only=False, config=None
+        url,
+        target,
+        recursive=False,
+        rev=None,
+        dvc_only=False,
+        config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
@@ -52,15 +80,40 @@ def test_list_outputs_only(mocker):
     url = "local_dir"
     m = _test_cli(mocker, url, None, "--dvc-only")
     m.assert_called_once_with(
-        url, None, recursive=False, rev=None, dvc_only=True, config=None
+        url,
+        None,
+        recursive=False,
+        rev=None,
+        dvc_only=True,
+        config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
 def test_list_config(mocker):
     url = "local_dir"
-    m = _test_cli(mocker, url, None, "--config", "myconfig")
+    m = _test_cli(
+        mocker,
+        url,
+        None,
+        "--config",
+        "myconfig",
+        "--remote",
+        "myremote",
+        "--remote-config",
+        "k1=v1",
+        "k2=v2",
+    )
     m.assert_called_once_with(
-        url, None, recursive=False, rev=None, dvc_only=False, config="myconfig"
+        url,
+        None,
+        recursive=False,
+        rev=None,
+        dvc_only=False,
+        config="myconfig",
+        remote="myremote",
+        remote_config={"k1": "v1", "k2": "v2"},
     )
 
 
diff --git a/tests/unit/command/test_get.py b/tests/unit/command/test_get.py
index 3e2cfad50..05e77b747 100644
--- a/tests/unit/command/test_get.py
+++ b/tests/unit/command/test_get.py
@@ -16,6 +16,11 @@ def test_get(mocker):
             "4",
             "--config",
             "myconfig",
+            "--remote",
+            "myremote",
+            "--remote-config",
+            "k1=v1",
+            "k2=v2",
         ]
     )
     assert cli_args.func == CmdGet
@@ -33,6 +38,8 @@ def test_get(mocker):
         jobs=4,
         config="myconfig",
         force=False,
+        remote="myremote",
+        remote_config={"k1": "v1", "k2": "v2"},
     )
 
 
diff --git a/tests/unit/command/test_imp.py b/tests/unit/command/test_imp.py
index 83b49d8bd..b6aa9bc7b 100644
--- a/tests/unit/command/test_imp.py
+++ b/tests/unit/command/test_imp.py
@@ -16,6 +16,11 @@ def test_import(mocker, dvc):
             "3",
             "--config",
             "myconfig",
+            "--remote",
+            "myremote",
+            "--remote-config",
+            "k1=v1",
+            "k2=v2",
         ]
     )
     assert cli_args.func == CmdImport
@@ -34,6 +39,8 @@ def test_import(mocker, dvc):
         no_download=False,
         jobs=3,
         config="myconfig",
+        remote="myremote",
+        remote_config={"k1": "v1", "k2": "v2"},
     )
 
 
@@ -65,6 +72,8 @@ def test_import_no_exec(mocker, dvc):
         no_download=False,
         jobs=None,
         config=None,
+        remote=None,
+        remote_config=None,
     )
 
 
@@ -96,4 +105,6 @@ def test_import_no_download(mocker, dvc):
         no_download=True,
         jobs=None,
         config=None,
+        remote=None,
+        remote_config=None,
     )