Skip to content

Commit

Permalink
Use Ruff linter and consolidate tox stuff to pyproject.toml (#266)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
facelessuser authored Aug 31, 2023
1 parent 230be63 commit 62da3cd
Show file tree
Hide file tree
Showing 20 changed files with 158 additions and 171 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.9]
python-version: [3.11]

env:
TOXENV: lint
Expand All @@ -106,7 +106,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.9]
python-version: [3.11]

env:
TOXENV: documents
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.9]
python-version: [3.11]

runs-on: ubuntu-latest

Expand Down Expand Up @@ -43,7 +43,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: 3.11
- name: Package
run: |
pip install --upgrade build
Expand Down
12 changes: 3 additions & 9 deletions docs/src/markdown/about/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,9 @@ Directory | Description

## Coding Standards

When writing code, the code should roughly conform to PEP8 and PEP257 suggestions. The project utilizes the Flake8
linter (with some additional plugins) to ensure code conforms (give or take some of the rules). When in doubt, follow
the formatting hints of existing code when adding files or modifying existing files. Listed below are the modules used:

- @gitlab:pycqa/flake8
- @gitlab:pycqa/flake8-docstrings
- @gitlab:pycqa/pep8-naming
- @ebeweber/flake8-mutable
- @gforcada/flake8-builtins
When writing code, the code should roughly conform to PEP8 and PEP257 suggestions along with some other requirements.
The project utilizes the @astral-sh/ruff linter that helps to ensure code conforms (give or take some of the rules).
When in doubt, follow the formatting hints of existing code when adding files or modifying existing files.

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

Expand Down
12 changes: 0 additions & 12 deletions hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,12 @@ def get_version_dev_status(root):
return module.__version_info__._get_dev_status()


def get_requirements(root):
"""Load list of dependencies."""

install_requires = []
with open(os.path.join(root, "requirements", "project.txt")) as f:
for line in f:
if not line.startswith("#"):
install_requires.append(line.strip())
return install_requires


class CustomMetadataHook(MetadataHookInterface):
"""Our metadata hook."""

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

metadata["dependencies"] = get_requirements(self.root)
metadata["classifiers"] = [
f"Development Status :: {get_version_dev_status(self.root)}",
'Environment :: Console',
Expand Down
98 changes: 95 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ keywords = [
]
dynamic = [
"classifiers",
"dependencies",
"version",
]

Expand Down Expand Up @@ -55,8 +54,7 @@ include = [
"/tests/**/*.py",
"/.pyspelling.yml",
"/.coveragerc",
"/mkdocs.yml",
"/tox.ini",
"/mkdocs.yml"
]

[tool.mypy]
Expand All @@ -67,3 +65,97 @@ strict = true
show_error_codes = true

[tool.hatch.metadata.hooks.custom]

[tool.ruff]
line-length = 120

select = [
"A", # flake8-builtins
"B", # flake8-bugbear
"D", # pydocstyle
"C4", # flake8-comprehensions
"N", # pep8-naming
"E", # pycodestyle
"F", # pyflakes
"PGH", # pygrep-hooks
"RUF", # ruff
# "UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020,
"PERF" # Perflint
]

ignore = [
"E741",
"D202",
"D401",
"D212",
"D203",
"N802",
"N801",
"N803",
"N806",
"N818",
"RUF012",
"RUF005",
"PGH004",
"RUF100"
]

[tool.tox]
legacy_tox_ini = """
[tox]
isolated_build = true
envlist =
py{38,39,310,311,312},
lint, nolxml, nohtml5lib
[testenv]
passenv = *
deps =
-rrequirements/tests.txt
commands =
mypy
pytest --cov soupsieve --cov-append {toxinidir}
coverage html -d {envtmpdir}/coverage
coverage xml
coverage report --show-missing
[testenv:documents]
passenv = *
deps =
-rrequirements/docs.txt
commands =
mkdocs build --clean --verbose --strict
pyspelling
[testenv:lint]
passenv = *
deps =
-rrequirements/lint.txt
commands =
"{envbindir}"/ruff check .
[testenv:nolxml]
passenv = *
deps =
-rrequirements/tests-nolxml.txt
commands =
pytest {toxinidir}
[testenv:nohtml5lib]
passenv = *
deps =
-rrequirements/tests-nohtml5lib.txt
commands =
pytest {toxinidir}
[flake8]
exclude=build/*,.tox/*
max-line-length=120
ignore=D202,D203,D401,E741,W504,N817,N818
[pytest]
filterwarnings =
ignore:\nCSS selector pattern:UserWarning
"""
7 changes: 1 addition & 6 deletions requirements/lint.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
flake8
pydocstyle<4.0.0
flake8_docstrings
pep8-naming
flake8-mutable
flake8-builtins
ruff
Empty file removed requirements/project.txt
Empty file.
8 changes: 2 additions & 6 deletions soupsieve/css_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def has_html_ns(el: bs4.Tag) -> bool:
like we do in the case of `is_html_tag`.
"""

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

@staticmethod
Expand Down Expand Up @@ -1271,11 +1271,7 @@ def match_dir(self, el: bs4.Tag, directionality: int) -> bool:
# Auto handling for text inputs
if ((is_input and itype in ('text', 'search', 'tel', 'url', 'email')) or is_textarea) and direction == 0:
if is_textarea:
temp = []
for node in self.get_contents(el, no_iframe=True):
if self.is_content_string(node):
temp.append(node)
value = ''.join(temp)
value = ''.join(node for node in self.get_contents(el, no_iframe=True) if self.is_content_string(node))
else:
value = cast(str, self.get_attribute_by_name(el, 'value', ''))
if value:
Expand Down
2 changes: 1 addition & 1 deletion soupsieve/css_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ def parse_pseudo_contains(self, sel: _Selector, m: Match[str], has_selector: boo

pseudo = util.lower(css_unescape(m.group('name')))
if pseudo == ":contains":
warnings.warn(
warnings.warn( # noqa: B028
"The pseudo class ':contains' is deprecated, ':-soup-contains' should be used moving forward.",
FutureWarning
)
Expand Down
18 changes: 9 additions & 9 deletions soupsieve/css_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ def __eq__(self, other: Any) -> bool:

return (
isinstance(other, self.__base__()) and
all([getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash'])
all(getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash')
)

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

return (
not isinstance(other, self.__base__()) or
any([getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash'])
any(getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash')
)

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

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

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

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


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

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


Expand Down Expand Up @@ -367,7 +367,7 @@ def __init__(
"""Initialize."""

super().__init__(
selectors=tuple(selectors) if selectors is not None else tuple(),
selectors=tuple(selectors) if selectors is not None else (),
is_not=is_not,
is_html=is_html
)
Expand Down
3 changes: 2 additions & 1 deletion soupsieve/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
hasn't been tested extensively to make sure we aren't missing corners).
Example:
-------
```
>>> import soupsieve as sv
>>> sv.compile('this > that.class[name=value]').selectors.pretty()
Expand Down Expand Up @@ -64,6 +64,7 @@
is_not=False,
is_html=False)
```
"""
from __future__ import annotations
import re
Expand Down
26 changes: 8 additions & 18 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ def test_select(self):
"""

soup = self.soup(markup, 'html.parser')
ids = []
for el in sv.select('span[id]', soup):
ids.append(el.attrs['id'])
ids = [el.attrs['id'] for el in sv.select('span[id]', soup)]

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

Expand All @@ -58,9 +56,7 @@ def test_select_order(self):
"""

soup = self.soup(markup, 'html.parser')
ids = []
for el in sv.select('[id]', soup.body):
ids.append(el.attrs['id'])
ids = [el.attrs['id'] for el in sv.select('[id]', soup.body)]

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

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

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

ids = []
for el in sv.select('span[id]', soup, limit=1):
ids.append(el.attrs['id'])
ids = [el.attrs['id'] for el in sv.select('span[id]', soup, limit=1)]

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

Expand Down Expand Up @@ -163,9 +157,7 @@ def test_iselect(self):

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

ids = []
for el in sv.iselect('span[id]', soup):
ids.append(el.attrs['id'])
ids = [el.attrs['id'] for el in sv.iselect('span[id]', soup)]

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

Expand All @@ -190,9 +182,7 @@ def test_iselect_order(self):
"""

soup = self.soup(markup, 'html.parser')
ids = []
for el in sv.iselect('[id]', soup):
ids.append(el.attrs['id'])
ids = [el.attrs['id'] for el in sv.iselect('[id]', soup)]

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

Expand Down Expand Up @@ -297,7 +287,7 @@ def test_filter_list(self):
"""

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

Expand Down Expand Up @@ -462,8 +452,8 @@ def test_cache(self):

sv.purge()
self.assertEqual(sv.cp._cached_css_compile.cache_info().currsize, 0)
for x in range(1000):
value = f'[value="{str(random.randint(1, 10000))}"]'
for _x in range(1000):
value = f'[value="{random.randint(1, 10000)!s}"]'
p = sv.compile(value)
self.assertTrue(p.pattern == value)
self.assertTrue(sv.cp._cached_css_compile.cache_info().currsize > 0)
Expand Down
Loading

0 comments on commit 62da3cd

Please sign in to comment.