Skip to content

Commit 32359ad

Browse files
authored
Ruff rules: Stricter code quality rules (#417)
2 parents 583b207 + d9e3659 commit 32359ad

23 files changed

+176
-127
lines changed

CHANGES

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ $ pipx install --suffix=@next 'vcspull' --pip-args '\--pre' --force
2121

2222
<!-- Maintainers, insert changes / features for the next release here -->
2323

24+
### Development
25+
26+
- Code quality improved via [ruff] rules (#147)
27+
28+
This includes fixes made by hand, and with ruff's automated fixes. Despite
29+
selecting additional rules, which include import sorting, ruff runs nearly
30+
instantaneously when checking the whole codebase.
31+
2432
## vcspull v1.21.1 (2023-05-28)
2533

2634
_Maintenance only, no bug fixes, or new features_

conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@pytest.fixture(autouse=True)
99
def add_doctest_fixtures(
1010
request: pytest.FixtureRequest,
11-
doctest_namespace: t.Dict[str, t.Any],
11+
doctest_namespace: dict[str, t.Any],
1212
) -> None:
1313
from _pytest.doctest import DoctestItem
1414

docs/conf.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# flake8: noqa: E501
22
import inspect
3+
import pathlib
34
import sys
45
import typing as t
56
from os.path import relpath
6-
import pathlib
77

88
import vcspull
99

@@ -178,9 +178,7 @@
178178
}
179179

180180

181-
def linkcode_resolve(
182-
domain: str, info: dict[str, str]
183-
) -> t.Union[None, str]: # NOQA: C901
181+
def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
184182
"""
185183
Determine the URL corresponding to Python object
186184
@@ -203,7 +201,7 @@ def linkcode_resolve(
203201
for part in fullname.split("."):
204202
try:
205203
obj = getattr(obj, part)
206-
except Exception:
204+
except Exception: # noqa: PERF203
207205
return None
208206

209207
# strip decorators, which would resolve to the source of the decorator
@@ -228,10 +226,7 @@ def linkcode_resolve(
228226
except Exception:
229227
lineno = None
230228

231-
if lineno:
232-
linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1)
233-
else:
234-
linespec = ""
229+
linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) if lineno else ""
235230

236231
fn = relpath(fn, start=pathlib.Path(vcspull.__file__).parent)
237232

pyproject.toml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ gp-libs = "*"
7272
sphinx-autobuild = "*"
7373
sphinx-autodoc-typehints = "*"
7474
sphinx-inline-tabs = "*"
75-
sphinxext-opengraph = "<0.8" # https://github.com/wpilibsuite/sphinxext-opengraph/issues/100
75+
sphinxext-opengraph = "<0.8" # https://github.com/wpilibsuite/sphinxext-opengraph/issues/100
7676
sphinx-copybutton = "*"
7777
sphinxext-rediraffe = "*"
7878
sphinx-argparse = "*"
@@ -128,7 +128,7 @@ python_version = 3.9
128128
warn_unused_configs = true
129129
files = [
130130
"src",
131-
"tests"
131+
"tests",
132132
]
133133
strict = true
134134

@@ -161,6 +161,32 @@ exclude_lines = [
161161
"@overload( |$)",
162162
]
163163

164+
[tool.ruff]
165+
target-version = "py39"
166+
select = [
167+
"E", # pycodestyle
168+
"F", # pyflakes
169+
"I", # isort
170+
"UP", # pyupgrade
171+
"B", # flake8-bugbear
172+
"C4", # flake8-comprehensions
173+
"Q", # flake8-quotes
174+
"PTH", # flake8-use-pathlib
175+
"SIM", # flake8-simplify
176+
"TRY", # Trycertatops
177+
"PERF", # Perflint
178+
"RUF", # Ruff-specific rules
179+
]
180+
181+
[tool.ruff.isort]
182+
known-first-party = [
183+
"vcspull",
184+
]
185+
combine-as-imports = true
186+
187+
[tool.ruff.per-file-ignores]
188+
"*/__init__.py" = ["F401"]
189+
164190
[build-system]
165191
requires = ["poetry_core>=1.0.0", "setuptools>50"]
166192
build-backend = "poetry.core.masonry.api"

scripts/generate_gitlab.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
import argparse
44
import os
5+
import pathlib
56
import sys
67

78
import requests
89
import yaml
9-
1010
from libvcs.sync.git import GitRemote
11-
from vcspull.cli.sync import guess_vcs
11+
12+
from vcspull.cli.sync import CouldNotGuessVCSFromURL, guess_vcs
1213
from vcspull.types import RawConfig
1314

1415
try:
@@ -39,10 +40,10 @@
3940
args = vars(parser.parse_args())
4041
gitlab_host = args["gitlab_host"]
4142
gitlab_namespace = args["gitlab_namespace"]
42-
config_filename = args["config_file_name"]
43+
config_filename = pathlib.Path(args["config_file_name"])
4344

4445
try:
45-
if os.path.isfile(config_filename):
46+
if config_filename.is_file():
4647
result = input(
4748
"The target config file (%s) already exists, \
4849
do you want to overwrite it? [y/N] "
@@ -57,24 +58,25 @@
5758
)
5859
sys.exit(0)
5960

60-
config_file = open(config_filename, "w")
61-
except IOError:
62-
print("File %s not accesible" % (config_filename))
61+
config_file = config_filename.open(mode="w")
62+
except OSError:
63+
print(f"File {config_filename} not accesible")
6364
sys.exit(1)
6465

6566
response = requests.get(
66-
"%s/api/v4/groups/%s/projects" % (gitlab_host, gitlab_namespace),
67+
f"{gitlab_host}/api/v4/groups/{gitlab_namespace}/projects",
6768
params={"include_subgroups": "true", "per_page": "100"},
6869
headers={"Authorization": "Bearer %s" % (gitlab_token)},
6970
)
7071

71-
if 200 != response.status_code:
72+
if response.status_code != 200:
7273
print("Error: ", response)
7374
sys.exit(1)
7475

75-
path_prefix = os.getcwd()
76+
path_prefix = pathlib.Path().cwd()
7677
config: RawConfig = {}
7778

79+
7880
for group in response.json():
7981
url_to_repo = group["ssh_url_to_repo"].replace(":", "/")
8082
namespace_path = group["namespace"]["full_path"]
@@ -90,7 +92,7 @@
9092

9193
vcs = guess_vcs(url_to_repo)
9294
if vcs is None:
93-
raise Exception(f"Could not guess VCS for URL: {url_to_repo}")
95+
raise CouldNotGuessVCSFromURL(url_to_repo)
9496

9597
config[path][reponame] = {
9698
"name": reponame,

src/vcspull/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
import logging
1313
from logging import NullHandler
1414

15-
from . import cli # NOQA
15+
from . import cli
1616

1717
logging.getLogger(__name__).addHandler(NullHandler())

src/vcspull/_internal/config_reader.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
FormatLiteral = Literal["json", "yaml"]
1111

12-
RawConfigData: TypeAlias = t.Dict[t.Any, t.Any]
12+
RawConfigData: TypeAlias = dict[t.Any, t.Any]
1313

1414

1515
class ConfigReader:
@@ -26,7 +26,7 @@ def __init__(self, content: "RawConfigData") -> None:
2626
self.content = content
2727

2828
@staticmethod
29-
def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
29+
def _load(format: "FormatLiteral", content: str) -> dict[str, t.Any]:
3030
"""Load raw config data and directly return it.
3131
3232
>>> ConfigReader._load("json", '{ "session_name": "my session" }')
@@ -37,14 +37,14 @@ def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
3737
"""
3838
if format == "yaml":
3939
return t.cast(
40-
t.Dict[str, t.Any],
40+
dict[str, t.Any],
4141
yaml.load(
4242
content,
4343
Loader=yaml.SafeLoader,
4444
),
4545
)
4646
elif format == "json":
47-
return t.cast(t.Dict[str, t.Any], json.loads(content))
47+
return t.cast(dict[str, t.Any], json.loads(content))
4848
else:
4949
raise NotImplementedError(f"{format} not supported in configuration")
5050

@@ -72,7 +72,7 @@ def load(cls, format: "FormatLiteral", content: str) -> "ConfigReader":
7272
)
7373

7474
@classmethod
75-
def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]:
75+
def _from_file(cls, path: pathlib.Path) -> dict[str, t.Any]:
7676
r"""Load data from file path directly to dictionary.
7777
7878
**YAML file**
@@ -102,7 +102,7 @@ def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]:
102102
{'session_name': 'my session'}
103103
"""
104104
assert isinstance(path, pathlib.Path)
105-
content = open(path).read()
105+
content = path.open().read()
106106

107107
if path.suffix in [".yaml", ".yml"]:
108108
format: "FormatLiteral" = "yaml"

src/vcspull/cli/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@overload
3636
def create_parser(
3737
return_subparsers: t.Literal[True],
38-
) -> t.Tuple[argparse.ArgumentParser, t.Any]:
38+
) -> tuple[argparse.ArgumentParser, t.Any]:
3939
...
4040

4141

@@ -46,7 +46,7 @@ def create_parser(return_subparsers: t.Literal[False]) -> argparse.ArgumentParse
4646

4747
def create_parser(
4848
return_subparsers: bool = False,
49-
) -> t.Union[argparse.ArgumentParser, t.Tuple[argparse.ArgumentParser, t.Any]]:
49+
) -> t.Union[argparse.ArgumentParser, tuple[argparse.ArgumentParser, t.Any]]:
5050
parser = argparse.ArgumentParser(
5151
prog="vcspull",
5252
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -80,7 +80,7 @@ def create_parser(
8080
return parser
8181

8282

83-
def cli(_args: t.Optional[t.List[str]] = None) -> None:
83+
def cli(_args: t.Optional[list[str]] = None) -> None:
8484
parser, sync_parser = create_parser(return_subparsers=True)
8585
args = parser.parse_args(_args)
8686

src/vcspull/cli/sync.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from libvcs.sync.git import GitSync
1212
from libvcs.url import registry as url_tools
1313

14+
from .. import exc
1415
from ..config import filter_repos, find_config_files, load_configs
1516

1617
log = logging.getLogger(__name__)
@@ -55,7 +56,7 @@ def create_sync_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP
5556

5657

5758
def sync(
58-
repo_patterns: t.List[str],
59+
repo_patterns: list[str],
5960
config: pathlib.Path,
6061
exit_on_error: bool,
6162
parser: t.Optional[
@@ -91,8 +92,8 @@ def sync(
9192
for repo in found_repos:
9293
try:
9394
update_repo(repo)
94-
except Exception:
95-
print(
95+
except Exception as e: # noqa: PERF203
96+
log.info(
9697
f'Failed syncing {repo.get("name")}',
9798
)
9899
if log.isEnabledFor(logging.DEBUG):
@@ -102,8 +103,7 @@ def sync(
102103
if exit_on_error:
103104
if parser is not None:
104105
parser.exit(status=1, message=EXIT_ON_ERROR_MSG)
105-
else:
106-
raise SystemExit(EXIT_ON_ERROR_MSG)
106+
raise SystemExit(EXIT_ON_ERROR_MSG) from e
107107

108108

109109
def progress_cb(output: str, timestamp: datetime) -> None:
@@ -124,6 +124,11 @@ def guess_vcs(url: str) -> t.Optional[VCSLiteral]:
124124
return t.cast(VCSLiteral, vcs_matches[0].vcs)
125125

126126

127+
class CouldNotGuessVCSFromURL(exc.VCSPullException):
128+
def __init__(self, repo_url: str, *args: object, **kwargs: object) -> None:
129+
return super().__init__(f"Could not automatically determine VCS for {repo_url}")
130+
131+
127132
def update_repo(
128133
repo_dict: t.Any,
129134
# repo_dict: Dict[str, Union[str, Dict[str, GitRemote], pathlib.Path]]
@@ -138,9 +143,7 @@ def update_repo(
138143
if repo_dict.get("vcs") is None:
139144
vcs = guess_vcs(url=repo_dict["url"])
140145
if vcs is None:
141-
raise Exception(
142-
f'Could not automatically determine VCS for {repo_dict["url"]}'
143-
)
146+
raise CouldNotGuessVCSFromURL(repo_url=repo_dict["url"])
144147

145148
repo_dict["vcs"] = vcs
146149

0 commit comments

Comments
 (0)