Skip to content

Commit 62da3cd

Browse files
authored
Use Ruff linter and consolidate tox stuff to pyproject.toml (#266)
* Use Ruff linter and consolidate tox stuff to pyproject.toml * Use Python 3.11 for generic actions * Don't try to bundle tox.ini as it is now gone
1 parent 230be63 commit 62da3cd

20 files changed

+158
-171
lines changed

.github/workflows/build.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
strategy:
8383
max-parallel: 4
8484
matrix:
85-
python-version: [3.9]
85+
python-version: [3.11]
8686

8787
env:
8888
TOXENV: lint
@@ -106,7 +106,7 @@ jobs:
106106
strategy:
107107
max-parallel: 4
108108
matrix:
109-
python-version: [3.9]
109+
python-version: [3.11]
110110

111111
env:
112112
TOXENV: documents

.github/workflows/deploy.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
max-parallel: 4
1313
matrix:
14-
python-version: [3.9]
14+
python-version: [3.11]
1515

1616
runs-on: ubuntu-latest
1717

@@ -43,7 +43,7 @@ jobs:
4343
- uses: actions/checkout@v3
4444
- uses: actions/setup-python@v4
4545
with:
46-
python-version: 3.9
46+
python-version: 3.11
4747
- name: Package
4848
run: |
4949
pip install --upgrade build

docs/src/markdown/about/development.md

+3-9
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,9 @@ Directory | Description
2525

2626
## Coding Standards
2727

28-
When writing code, the code should roughly conform to PEP8 and PEP257 suggestions. The project utilizes the Flake8
29-
linter (with some additional plugins) to ensure code conforms (give or take some of the rules). When in doubt, follow
30-
the formatting hints of existing code when adding files or modifying existing files. Listed below are the modules used:
31-
32-
- @gitlab:pycqa/flake8
33-
- @gitlab:pycqa/flake8-docstrings
34-
- @gitlab:pycqa/pep8-naming
35-
- @ebeweber/flake8-mutable
36-
- @gforcada/flake8-builtins
28+
When writing code, the code should roughly conform to PEP8 and PEP257 suggestions along with some other requirements.
29+
The project utilizes the @astral-sh/ruff linter that helps to ensure code conforms (give or take some of the rules).
30+
When in doubt, follow the formatting hints of existing code when adding files or modifying existing files.
3731

3832
Usually this can be automated with Tox (assuming it is installed): `tox -e lint`.
3933

hatch_build.py

-12
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,12 @@ def get_version_dev_status(root):
1616
return module.__version_info__._get_dev_status()
1717

1818

19-
def get_requirements(root):
20-
"""Load list of dependencies."""
21-
22-
install_requires = []
23-
with open(os.path.join(root, "requirements", "project.txt")) as f:
24-
for line in f:
25-
if not line.startswith("#"):
26-
install_requires.append(line.strip())
27-
return install_requires
28-
29-
3019
class CustomMetadataHook(MetadataHookInterface):
3120
"""Our metadata hook."""
3221

3322
def update(self, metadata):
3423
"""See https://ofek.dev/hatch/latest/plugins/metadata-hook/ for more information."""
3524

36-
metadata["dependencies"] = get_requirements(self.root)
3725
metadata["classifiers"] = [
3826
f"Development Status :: {get_version_dev_status(self.root)}",
3927
'Environment :: Console',

pyproject.toml

+95-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ keywords = [
2424
]
2525
dynamic = [
2626
"classifiers",
27-
"dependencies",
2827
"version",
2928
]
3029

@@ -55,8 +54,7 @@ include = [
5554
"/tests/**/*.py",
5655
"/.pyspelling.yml",
5756
"/.coveragerc",
58-
"/mkdocs.yml",
59-
"/tox.ini",
57+
"/mkdocs.yml"
6058
]
6159

6260
[tool.mypy]
@@ -67,3 +65,97 @@ strict = true
6765
show_error_codes = true
6866

6967
[tool.hatch.metadata.hooks.custom]
68+
69+
[tool.ruff]
70+
line-length = 120
71+
72+
select = [
73+
"A", # flake8-builtins
74+
"B", # flake8-bugbear
75+
"D", # pydocstyle
76+
"C4", # flake8-comprehensions
77+
"N", # pep8-naming
78+
"E", # pycodestyle
79+
"F", # pyflakes
80+
"PGH", # pygrep-hooks
81+
"RUF", # ruff
82+
# "UP", # pyupgrade
83+
"W", # pycodestyle
84+
"YTT", # flake8-2020,
85+
"PERF" # Perflint
86+
]
87+
88+
ignore = [
89+
"E741",
90+
"D202",
91+
"D401",
92+
"D212",
93+
"D203",
94+
"N802",
95+
"N801",
96+
"N803",
97+
"N806",
98+
"N818",
99+
"RUF012",
100+
"RUF005",
101+
"PGH004",
102+
"RUF100"
103+
]
104+
105+
[tool.tox]
106+
legacy_tox_ini = """
107+
[tox]
108+
isolated_build = true
109+
envlist =
110+
py{38,39,310,311,312},
111+
lint, nolxml, nohtml5lib
112+
113+
[testenv]
114+
passenv = *
115+
deps =
116+
-rrequirements/tests.txt
117+
commands =
118+
mypy
119+
pytest --cov soupsieve --cov-append {toxinidir}
120+
coverage html -d {envtmpdir}/coverage
121+
coverage xml
122+
coverage report --show-missing
123+
124+
[testenv:documents]
125+
passenv = *
126+
deps =
127+
-rrequirements/docs.txt
128+
commands =
129+
mkdocs build --clean --verbose --strict
130+
pyspelling
131+
132+
[testenv:lint]
133+
passenv = *
134+
deps =
135+
-rrequirements/lint.txt
136+
commands =
137+
"{envbindir}"/ruff check .
138+
139+
[testenv:nolxml]
140+
passenv = *
141+
deps =
142+
-rrequirements/tests-nolxml.txt
143+
commands =
144+
pytest {toxinidir}
145+
146+
[testenv:nohtml5lib]
147+
passenv = *
148+
deps =
149+
-rrequirements/tests-nohtml5lib.txt
150+
commands =
151+
pytest {toxinidir}
152+
153+
[flake8]
154+
exclude=build/*,.tox/*
155+
max-line-length=120
156+
ignore=D202,D203,D401,E741,W504,N817,N818
157+
158+
[pytest]
159+
filterwarnings =
160+
ignore:\nCSS selector pattern:UserWarning
161+
"""

requirements/lint.txt

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
flake8
2-
pydocstyle<4.0.0
3-
flake8_docstrings
4-
pep8-naming
5-
flake8-mutable
6-
flake8-builtins
1+
ruff

requirements/project.txt

Whitespace-only changes.

soupsieve/css_match.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def has_html_ns(el: bs4.Tag) -> bool:
282282
like we do in the case of `is_html_tag`.
283283
"""
284284

285-
ns = getattr(el, 'namespace') if el else None
285+
ns = getattr(el, 'namespace') if el else None # noqa: B009
286286
return bool(ns and ns == NS_XHTML)
287287

288288
@staticmethod
@@ -1271,11 +1271,7 @@ def match_dir(self, el: bs4.Tag, directionality: int) -> bool:
12711271
# Auto handling for text inputs
12721272
if ((is_input and itype in ('text', 'search', 'tel', 'url', 'email')) or is_textarea) and direction == 0:
12731273
if is_textarea:
1274-
temp = []
1275-
for node in self.get_contents(el, no_iframe=True):
1276-
if self.is_content_string(node):
1277-
temp.append(node)
1278-
value = ''.join(temp)
1274+
value = ''.join(node for node in self.get_contents(el, no_iframe=True) if self.is_content_string(node))
12791275
else:
12801276
value = cast(str, self.get_attribute_by_name(el, 'value', ''))
12811277
if value:

soupsieve/css_parser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ def parse_pseudo_contains(self, sel: _Selector, m: Match[str], has_selector: boo
869869

870870
pseudo = util.lower(css_unescape(m.group('name')))
871871
if pseudo == ":contains":
872-
warnings.warn(
872+
warnings.warn( # noqa: B028
873873
"The pseudo class ':contains' is deprecated, ':-soup-contains' should be used moving forward.",
874874
FutureWarning
875875
)

soupsieve/css_types.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ def __eq__(self, other: Any) -> bool:
5959

6060
return (
6161
isinstance(other, self.__base__()) and
62-
all([getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash'])
62+
all(getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash')
6363
)
6464

6565
def __ne__(self, other: Any) -> bool:
6666
"""Equal."""
6767

6868
return (
6969
not isinstance(other, self.__base__()) or
70-
any([getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash'])
70+
any(getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash')
7171
)
7272

7373
def __hash__(self) -> int:
@@ -112,9 +112,9 @@ def _validate(self, arg: dict[Any, Any] | Iterable[tuple[Any, Any]]) -> None:
112112
"""Validate arguments."""
113113

114114
if isinstance(arg, dict):
115-
if not all([isinstance(v, Hashable) for v in arg.values()]):
115+
if not all(isinstance(v, Hashable) for v in arg.values()):
116116
raise TypeError(f'{self.__class__.__name__} values must be hashable')
117-
elif not all([isinstance(k, Hashable) and isinstance(v, Hashable) for k, v in arg]):
117+
elif not all(isinstance(k, Hashable) and isinstance(v, Hashable) for k, v in arg):
118118
raise TypeError(f'{self.__class__.__name__} values must be hashable')
119119

120120
def __iter__(self) -> Iterator[Any]:
@@ -157,9 +157,9 @@ def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
157157
"""Validate arguments."""
158158

159159
if isinstance(arg, dict):
160-
if not all([isinstance(v, str) for v in arg.values()]):
160+
if not all(isinstance(v, str) for v in arg.values()):
161161
raise TypeError(f'{self.__class__.__name__} values must be hashable')
162-
elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]):
162+
elif not all(isinstance(k, str) and isinstance(v, str) for k, v in arg):
163163
raise TypeError(f'{self.__class__.__name__} keys and values must be Unicode strings')
164164

165165

@@ -175,9 +175,9 @@ def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None:
175175
"""Validate arguments."""
176176

177177
if isinstance(arg, dict):
178-
if not all([isinstance(v, str) for v in arg.values()]):
178+
if not all(isinstance(v, str) for v in arg.values()):
179179
raise TypeError(f'{self.__class__.__name__} values must be hashable')
180-
elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]):
180+
elif not all(isinstance(k, str) and isinstance(v, str) for k, v in arg):
181181
raise TypeError(f'{self.__class__.__name__} keys and values must be Unicode strings')
182182

183183

@@ -367,7 +367,7 @@ def __init__(
367367
"""Initialize."""
368368

369369
super().__init__(
370-
selectors=tuple(selectors) if selectors is not None else tuple(),
370+
selectors=tuple(selectors) if selectors is not None else (),
371371
is_not=is_not,
372372
is_html=is_html
373373
)

soupsieve/pretty.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
hasn't been tested extensively to make sure we aren't missing corners).
1111
1212
Example:
13-
13+
-------
1414
```
1515
>>> import soupsieve as sv
1616
>>> sv.compile('this > that.class[name=value]').selectors.pretty()
@@ -64,6 +64,7 @@
6464
is_not=False,
6565
is_html=False)
6666
```
67+
6768
"""
6869
from __future__ import annotations
6970
import re

tests/test_api.py

+8-18
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ def test_select(self):
3131
"""
3232

3333
soup = self.soup(markup, 'html.parser')
34-
ids = []
35-
for el in sv.select('span[id]', soup):
36-
ids.append(el.attrs['id'])
34+
ids = [el.attrs['id'] for el in sv.select('span[id]', soup)]
3735

3836
self.assertEqual(sorted(['5', 'some-id']), sorted(ids))
3937

@@ -58,9 +56,7 @@ def test_select_order(self):
5856
"""
5957

6058
soup = self.soup(markup, 'html.parser')
61-
ids = []
62-
for el in sv.select('[id]', soup.body):
63-
ids.append(el.attrs['id'])
59+
ids = [el.attrs['id'] for el in sv.select('[id]', soup.body)]
6460

6561
self.assertEqual(['1', '2', '3', '4', '5', 'some-id', '6'], ids)
6662

@@ -86,9 +82,7 @@ def test_select_limit(self):
8682

8783
soup = self.soup(markup, 'html.parser')
8884

89-
ids = []
90-
for el in sv.select('span[id]', soup, limit=1):
91-
ids.append(el.attrs['id'])
85+
ids = [el.attrs['id'] for el in sv.select('span[id]', soup, limit=1)]
9286

9387
self.assertEqual(sorted(['5']), sorted(ids))
9488

@@ -163,9 +157,7 @@ def test_iselect(self):
163157

164158
soup = self.soup(markup, 'html.parser')
165159

166-
ids = []
167-
for el in sv.iselect('span[id]', soup):
168-
ids.append(el.attrs['id'])
160+
ids = [el.attrs['id'] for el in sv.iselect('span[id]', soup)]
169161

170162
self.assertEqual(sorted(['5', 'some-id']), sorted(ids))
171163

@@ -190,9 +182,7 @@ def test_iselect_order(self):
190182
"""
191183

192184
soup = self.soup(markup, 'html.parser')
193-
ids = []
194-
for el in sv.iselect('[id]', soup):
195-
ids.append(el.attrs['id'])
185+
ids = [el.attrs['id'] for el in sv.iselect('[id]', soup)]
196186

197187
self.assertEqual(['1', '2', '3', '4', '5', 'some-id', '6'], ids)
198188

@@ -297,7 +287,7 @@ def test_filter_list(self):
297287
"""
298288

299289
soup = self.soup(markup, 'html.parser')
300-
nodes = sv.filter('pre#\\36', [el for el in soup.html.body.children])
290+
nodes = sv.filter('pre#\\36', list(soup.html.body.children))
301291
self.assertEqual(len(nodes), 1)
302292
self.assertEqual(nodes[0].attrs['id'], '6')
303293

@@ -462,8 +452,8 @@ def test_cache(self):
462452

463453
sv.purge()
464454
self.assertEqual(sv.cp._cached_css_compile.cache_info().currsize, 0)
465-
for x in range(1000):
466-
value = f'[value="{str(random.randint(1, 10000))}"]'
455+
for _x in range(1000):
456+
value = f'[value="{random.randint(1, 10000)!s}"]'
467457
p = sv.compile(value)
468458
self.assertTrue(p.pattern == value)
469459
self.assertTrue(sv.cp._cached_css_compile.cache_info().currsize > 0)

0 commit comments

Comments
 (0)