Skip to content

Commit 4d86cbd

Browse files
authored
refactor: Stricter code quality rules (via ruff, #488)
pyflakes, pycodestyle, import sorting, and various other linters have been added.
2 parents a0cf146 + eb42b5b commit 4d86cbd

22 files changed

+337
-230
lines changed

Diff for: CHANGES

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ $ pip install --user --upgrade --pre libtmux
1616

1717
_Maintenance only, no bug fixes, or new features_
1818

19+
### Development
20+
21+
- Code quality improved via [ruff] rules (#488)
22+
23+
This includes fixes made by hand, and with ruff's automated fixes. Despite
24+
selecting additional rules, which include import sorting, ruff runs nearly
25+
instantaneously when checking the whole codebase.
26+
1927
## libtmux 0.22.2 (2023-08-20)
2028

2129
### Development

Diff for: conftest.py

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import typing as t
1313

1414
import pytest
15-
1615
from _pytest.doctest import DoctestItem
1716

1817
from libtmux.pytest_plugin import USING_ZSH

Diff for: docs/conf.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# flake8: NOQA: E501
22
import contextlib
33
import inspect
4-
import sys
5-
from os.path import relpath
64
import pathlib
5+
import sys
76
import typing as t
7+
from os.path import relpath
88

9-
import libtmux # NOQA
10-
from libtmux import test # NOQA
9+
import libtmux
1110

1211
if t.TYPE_CHECKING:
1312
from sphinx.application import Sphinx
@@ -166,9 +165,7 @@
166165
}
167166

168167

169-
def linkcode_resolve(
170-
domain: str, info: t.Dict[str, str]
171-
) -> t.Union[None, str]: # NOQA: C901
168+
def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]:
172169
"""
173170
Determine the URL corresponding to Python object
174171
@@ -191,7 +188,7 @@ def linkcode_resolve(
191188
for part in fullname.split("."):
192189
try:
193190
obj = getattr(obj, part)
194-
except Exception:
191+
except Exception: # noqa: PERF203
195192
return None
196193

197194
# strip decorators, which would resolve to the source of the decorator
@@ -216,10 +213,7 @@ def linkcode_resolve(
216213
except Exception:
217214
lineno = None
218215

219-
if lineno:
220-
linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1)
221-
else:
222-
linespec = ""
216+
linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) if lineno else ""
223217

224218
fn = relpath(fn, start=pathlib.Path(libtmux.__file__).parent)
225219

Diff for: pyproject.toml

+27
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,33 @@ exclude_lines = [
129129
"@overload( |$)",
130130
]
131131

132+
[tool.ruff]
133+
target-version = "py37"
134+
select = [
135+
"E", # pycodestyle
136+
"F", # pyflakes
137+
"I", # isort
138+
"UP", # pyupgrade
139+
"B", # flake8-bugbear
140+
"C4", # flake8-comprehensions
141+
"Q", # flake8-quotes
142+
"PTH", # flake8-use-pathlib
143+
"ERA", # eradicate
144+
"SIM", # flake8-simplify
145+
"TRY", # Trycertatops
146+
"PERF", # Perflint
147+
"RUF" # Ruff-specific rules
148+
]
149+
150+
[tool.ruff.isort]
151+
known-first-party = [
152+
"libtmux"
153+
]
154+
combine-as-imports = true
155+
156+
[tool.ruff.per-file-ignores]
157+
"*/__init__.py" = ["F401"]
158+
132159
[build-system]
133160
requires = ["poetry_core>=1.0.0"]
134161
build-backend = "poetry.core.masonry.api"

Diff for: src/libtmux/_internal/query_list.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ def keygetter(
5959
elif hasattr(dct, sub_field):
6060
dct = getattr(dct, sub_field)
6161

62-
return dct
6362
except Exception as e:
6463
traceback.print_stack()
6564
print(f"Above error was {e}")
66-
return None
65+
return None
66+
67+
return dct
6768

6869

6970
def parse_lookup(obj: "Mapping[str, Any]", path: str, lookup: str) -> Optional[Any]:
@@ -123,7 +124,7 @@ def lookup_icontains(
123124
if isinstance(data, str):
124125
return rhs.lower() in data.lower()
125126
if isinstance(data, Mapping):
126-
return rhs.lower() in [k.lower() for k in data.keys()]
127+
return rhs.lower() in [k.lower() for k in data]
127128

128129
return False
129130

@@ -183,7 +184,6 @@ def lookup_in(
183184
return rhs in data
184185
# TODO: Add a deep Mappingionary matcher
185186
# if isinstance(rhs, Mapping) and isinstance(data, Mapping):
186-
# return rhs.items() not in data.items()
187187
except Exception:
188188
return False
189189
return False
@@ -205,7 +205,6 @@ def lookup_nin(
205205
return rhs not in data
206206
# TODO: Add a deep Mappingionary matcher
207207
# if isinstance(rhs, Mapping) and isinstance(data, Mapping):
208-
# return rhs.items() not in data.items()
209208
except Exception:
210209
return False
211210
return False
@@ -246,6 +245,16 @@ def lookup_iregex(
246245
}
247246

248247

248+
class PKRequiredException(Exception):
249+
def __init__(self, *args: object):
250+
return super().__init__("items() require a pk_key exists")
251+
252+
253+
class OpNotFound(ValueError):
254+
def __init__(self, op: str, *args: object):
255+
return super().__init__(f"{op} not in LOOKUP_NAME_MAP")
256+
257+
249258
class QueryList(List[T]):
250259
"""Filter list of object/dictionaries. For small, local datasets.
251260
@@ -286,17 +295,13 @@ class QueryList(List[T]):
286295

287296
def items(self) -> List[T]:
288297
if self.pk_key is None:
289-
raise Exception("items() require a pk_key exists")
298+
raise PKRequiredException()
290299
return [(getattr(item, self.pk_key), item) for item in self]
291300

292301
def __eq__(
293302
self,
294303
other: object,
295304
# other: Union[
296-
# "QueryList[T]",
297-
# List[Mapping[str, str]],
298-
# List[Mapping[str, int]],
299-
# List[Mapping[str, Union[str, Mapping[str, Union[List[str], str]]]]],
300305
# ],
301306
) -> bool:
302307
data = other
@@ -330,7 +335,7 @@ def filter_lookup(obj: Any) -> bool:
330335
lhs, op = path.rsplit("__", 1)
331336

332337
if op not in LOOKUP_NAME_MAP:
333-
raise ValueError(f"{op} not in LOOKUP_NAME_MAP")
338+
raise OpNotFound(op=op)
334339
except ValueError:
335340
lhs = path
336341
op = "exact"

Diff for: src/libtmux/_vendor/version.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ class InvalidVersion(ValueError):
6262
libtmux._vendor.version.InvalidVersion: Invalid version: 'invalid'
6363
"""
6464

65+
def __init__(self, version: str, *args: object):
66+
return super().__init__(f"Invalid version: '{version}'")
67+
6568

6669
class _BaseVersion:
6770
_key: CmpKey
@@ -195,7 +198,7 @@ def __init__(self, version: str) -> None:
195198
# Validate the version and parse it into pieces
196199
match = self._regex.search(version)
197200
if not match:
198-
raise InvalidVersion(f"Invalid version: '{version}'")
201+
raise InvalidVersion(version=version)
199202

200203
# Store the parsed out pieces of the version
201204
self._version = _Version(

Diff for: src/libtmux/common.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def show_environment(self) -> Dict[str, Union[bool, str]]:
151151
elif len(_t) == 1:
152152
vars_dict[_t[0]] = True
153153
else:
154-
raise ValueError(f"unexpected variable {_t}")
154+
raise exc.VariableUnpackingError(variable=_t)
155155

156156
return vars_dict
157157

@@ -172,7 +172,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]:
172172
str
173173
Value of environment variable
174174
"""
175-
tmux_args: t.Tuple[t.Union[str, int], ...] = tuple()
175+
tmux_args: t.Tuple[t.Union[str, int], ...] = ()
176176

177177
tmux_args += ("show-environment",)
178178
if self._add_option:
@@ -188,7 +188,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]:
188188
elif len(_t) == 1:
189189
vars_dict[_t[0]] = True
190190
else:
191-
raise ValueError(f"unexpected variable {_t}")
191+
raise exc.VariableUnpackingError(variable=_t)
192192

193193
return vars_dict.get(name)
194194

@@ -242,8 +242,8 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
242242
)
243243
stdout, stderr = self.process.communicate()
244244
returncode = self.process.returncode
245-
except Exception as e:
246-
logger.error(f"Exception for {subprocess.list2cmdline(cmd)}: \n{e}")
245+
except Exception:
246+
logger.exception(f"Exception for {subprocess.list2cmdline(cmd)}")
247247
raise
248248

249249
self.returncode = returncode
@@ -425,9 +425,10 @@ def has_minimum_version(raises: bool = True) -> bool:
425425
if get_version() < LooseVersion(TMUX_MIN_VERSION):
426426
if raises:
427427
raise exc.VersionTooLow(
428-
"libtmux only supports tmux %s and greater. This system"
429-
" has %s installed. Upgrade your tmux to use libtmux."
430-
% (TMUX_MIN_VERSION, get_version())
428+
"libtmux only supports tmux {} and greater. This system"
429+
" has {} installed. Upgrade your tmux to use libtmux.".format(
430+
TMUX_MIN_VERSION, get_version()
431+
)
431432
)
432433
else:
433434
return False
@@ -452,15 +453,11 @@ def session_check_name(session_name: t.Optional[str]) -> None:
452453
Invalid session name.
453454
"""
454455
if session_name is None or len(session_name) == 0:
455-
raise exc.BadSessionName("tmux session names may not be empty.")
456+
raise exc.BadSessionName(reason="empty", session_name=session_name)
456457
elif "." in session_name:
457-
raise exc.BadSessionName(
458-
'tmux session name "%s" may not contain periods.', session_name
459-
)
458+
raise exc.BadSessionName(reason="contains periods", session_name=session_name)
460459
elif ":" in session_name:
461-
raise exc.BadSessionName(
462-
'tmux session name "%s" may not contain colons.', session_name
463-
)
460+
raise exc.BadSessionName(reason="contains colons", session_name=session_name)
464461

465462

466463
def handle_option_error(error: str) -> t.Type[exc.OptionError]:

0 commit comments

Comments
 (0)