From 2ec1ef557f92618d276b32065af36ccd09bffad5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Sat, 18 May 2024 15:22:01 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20Move=20to=20ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 23 +++++++--------------- docs/conf.py | 1 + pyproject.toml | 36 ++++++++++++++++++++++++++++++----- sphinx_design/__init__.py | 1 + sphinx_design/_compat.py | 1 + sphinx_design/article_info.py | 6 +++--- sphinx_design/cards.py | 31 +++++++++++++++++------------- sphinx_design/dropdown.py | 8 ++++---- sphinx_design/extension.py | 7 +++---- sphinx_design/grids.py | 16 ++++++++-------- sphinx_design/icons.py | 16 ++++++++-------- sphinx_design/tabs.py | 27 ++++++++++++-------------- tests/conftest.py | 9 +++++---- tests/test_snippets.py | 1 + tox.ini | 4 ---- 15 files changed, 103 insertions(+), 84 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4723d7..9374882 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,31 +11,22 @@ exclude: > repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-json - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/isort - rev: 5.12.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.4 hooks: - - id: isort - - - repo: https://github.com/psf/black - rev: 23.7.0 - hooks: - - id: black - - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: [flake8-bugbear~=22.7] + - id: ruff + args: [--fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.10.0 hooks: - id: mypy additional_dependencies: [] diff --git a/docs/conf.py b/docs/conf.py index 1f3d28b..6aa598a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ """Configuration file for the Sphinx documentation builder.""" + import os project = "Sphinx Design" diff --git a/pyproject.toml b/pyproject.toml index 703ebfc..6df46b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,37 @@ exclude = [ "tests/", ] +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + # "FURB",# refurb (modernising code) + "I", # isort + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "N", # pep8-naming + "PERF",# perflint (performance anti-patterns) + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PTH", # flake8-use-pathlib + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "UP", # pyupgrade + "T20", # flake8-print +] +extend-ignore = [ + "ISC001", # implicit-str-concat + "PLR2004", + "RUF012", +] + +# [tool.ruff.lint.per-file-ignores] +# "..." = ["N801"] + +[tool.ruff.lint.isort] +force-sort-within-sections = true + [tool.mypy] show_error_codes = true warn_unused_ignores = true @@ -64,8 +95,3 @@ strict_equality = true [[tool.mypy.overrides]] module = ["docutils.*"] ignore_missing_imports = true - -[tool.isort] -profile = "black" -src_paths = ["sphinx_design", "tests"] -force_sort_within_sections = true diff --git a/sphinx_design/__init__.py b/sphinx_design/__init__.py index e7260e3..b4a2b63 100644 --- a/sphinx_design/__init__.py +++ b/sphinx_design/__init__.py @@ -1,4 +1,5 @@ """A sphinx extension for designing beautiful, view size responsive web components.""" + from typing import TYPE_CHECKING __version__ = "0.5.0" diff --git a/sphinx_design/_compat.py b/sphinx_design/_compat.py index d31e631..f1bd875 100644 --- a/sphinx_design/_compat.py +++ b/sphinx_design/_compat.py @@ -1,4 +1,5 @@ """Helpers for cross compatibility across dependency versions.""" + from importlib import resources from typing import Callable, Iterable diff --git a/sphinx_design/article_info.py b/sphinx_design/article_info.py index 90d396a..f654be1 100644 --- a/sphinx_design/article_info.py +++ b/sphinx_design/article_info.py @@ -48,7 +48,7 @@ def _parse_text( output = [para] return output - def run(self) -> List[nodes.Node]: + def run(self) -> List[nodes.Node]: # noqa: PLR0915 """Run the directive.""" parse_fields = True # parse field text @@ -60,8 +60,8 @@ def run(self) -> List[nodes.Node]: "sd-p-0", "sd-mt-2", "sd-mb-4", - ] - + self.options.get("class-container", []), + *self.options.get("class-container", []), + ], ) self.set_source_info(top_grid) diff --git a/sphinx_design/cards.py b/sphinx_design/cards.py index 5b03bfb..b638fc2 100644 --- a/sphinx_design/cards.py +++ b/sphinx_design/cards.py @@ -77,7 +77,7 @@ def run(self) -> List[nodes.Node]: return [self.create_card(self, self.arguments, self.options)] @classmethod - def create_card( + def create_card( # noqa: PLR0912, PLR0915 cls, inst: SphinxDirective, arguments: Optional[list], options: dict ) -> nodes.Node: """Run the directive.""" @@ -118,7 +118,7 @@ def create_card( "", uri=options["img-top"], alt=img_alt, - classes=["sd-card-img-top"] + options.get("class-img-top", []), + classes=["sd-card-img-top", *options.get("class-img-top", [])], ) container.append(image_top) @@ -137,8 +137,11 @@ def create_card( if arguments: title = create_component( "card-title", - ["sd-card-title", "sd-font-weight-bold"] - + options.get("class-title", []), + [ + "sd-card-title", + "sd-font-weight-bold", + *options.get("class-title", []), + ], ) textnodes, _ = inst.state.inline_text(arguments[0], inst.lineno) title_container = PassthroughTextElement() @@ -160,7 +163,7 @@ def create_card( "", uri=options["img-bottom"], alt=img_alt, - classes=["sd-card-img-bottom"] + options.get("class-img-bottom", []), + classes=["sd-card-img-bottom", *options.get("class-img-bottom", [])], ) container.append(image_bottom) @@ -225,7 +228,7 @@ def split_content(content: StringList, offset: int) -> CardContent: return CardContent(body, header, footer) @classmethod - def _create_component( + def _create_component( # noqa: PLR0913 cls, inst: SphinxDirective, name: str, @@ -235,7 +238,7 @@ def _create_component( ) -> nodes.container: """Create the header, body, or footer.""" component = create_component( - f"card-{name}", [f"sd-card-{name}"] + options.get(f"class-{name}", []) + f"card-{name}", [f"sd-card-{name}", *options.get(f"class-{name}", [])] ) inst.set_source_info(component) # TODO set proper lines inst.state.nested_parse(content, offset, component) @@ -246,9 +249,7 @@ def _create_component( def add_card_child_classes(node): """Add classes to specific child nodes.""" for para in findall(node)(nodes.paragraph): - para["classes"] = ([] if "classes" not in para else para["classes"]) + [ - "sd-card-text" - ] + para["classes"] = [*para.get("classes", []), "sd-card-text"] # for title in findall(node)(nodes.title): # title["classes"] = ([] if "classes" not in title else title["classes"]) + [ # "sd-card-title" @@ -273,11 +274,15 @@ def run(self) -> List[nodes.Node]: self.arguments[0].strip() ) except ValueError as exc: - raise self.error(f"Invalid directive argument: {exc}") + raise self.error(f"Invalid directive argument: {exc}") from exc container = create_component( "card-carousel", - ["sd-sphinx-override", "sd-cards-carousel", f"sd-card-cols-{cols}"] - + self.options.get("class", []), + [ + "sd-sphinx-override", + "sd-cards-carousel", + f"sd-card-cols-{cols}", + *self.options.get("class", []), + ], ) self.set_source_info(container) self.state.nested_parse(self.content, self.content_offset, container) diff --git a/sphinx_design/dropdown.py b/sphinx_design/dropdown.py index ecd5c46..7759d4c 100644 --- a/sphinx_design/dropdown.py +++ b/sphinx_design/dropdown.py @@ -1,5 +1,5 @@ -"""Originally Adapted from sphinxcontrib.details.directive -""" +"""Originally Adapted from sphinxcontrib.details.directive""" + from docutils import nodes from docutils.parsers.rst import directives from sphinx.application import Sphinx @@ -25,11 +25,11 @@ def setup_dropdown(app: Sphinx) -> None: app.add_post_transform(DropdownHtmlTransform) -class dropdown_main(nodes.Element, nodes.General): +class dropdown_main(nodes.Element, nodes.General): # noqa: N801 pass -class dropdown_title(nodes.TextElement, nodes.General): +class dropdown_title(nodes.TextElement, nodes.General): # noqa: N801 pass diff --git a/sphinx_design/extension.py b/sphinx_design/extension.py index 962b1fb..607e39a 100644 --- a/sphinx_design/extension.py +++ b/sphinx_design/extension.py @@ -122,11 +122,10 @@ def run(self): classes = directives.class_option(self.arguments[0]) else: classes = [] - except ValueError: + except ValueError as exc: raise self.error( - 'Invalid class attribute value for "%s" directive: "%s".' - % (self.name, self.arguments[0]) - ) + f'Invalid class attribute value for "{self.name}" directive: "{self.arguments[0]}".' + ) from exc node = create_component("div", rawtext="\n".join(self.content), classes=classes) if "style" in self.options: node["style"] = self.options["style"] diff --git a/sphinx_design/grids.py b/sphinx_design/grids.py index caf7402..ccf2971 100644 --- a/sphinx_design/grids.py +++ b/sphinx_design/grids.py @@ -60,8 +60,8 @@ def _media_option( continue try: int_value = int(value) - except Exception: - raise ValueError(validate_error_msg) + except Exception as exc: + raise ValueError(validate_error_msg) from exc if not (min_num <= int_value <= max_num): raise ValueError(validate_error_msg) return [f"{prefix}{values[0]}"] + [ @@ -118,7 +118,7 @@ def run(self) -> List[nodes.Node]: row_columns_option(self.arguments[0]) if self.arguments else [] ) except ValueError as exc: - raise self.error(f"Invalid directive argument: {exc}") + raise self.error(f"Invalid directive argument: {exc}") from exc self.assert_has_content() # container-fluid is 100% width for all breakpoints, # rather than the fixed width of the breakpoint (like container) @@ -251,11 +251,11 @@ def run(self) -> List[nodes.Node]: [ "sd-col", "sd-d-flex-row", - ] - + self.options.get("columns", []) - + self.options.get("margin", []) - + self.options.get("padding", []) - + self.options.get("class-item", []), + *self.options.get("columns", []), + *self.options.get("margin", []), + *self.options.get("padding", []), + *self.options.get("class-item", []), + ], ) card_options = { key: value diff --git a/sphinx_design/icons.py b/sphinx_design/icons.py index b0595fc..2932a86 100644 --- a/sphinx_design/icons.py +++ b/sphinx_design/icons.py @@ -72,8 +72,8 @@ def get_octicon( """ try: data = get_octicon_data()[name] - except KeyError: - raise KeyError(f"Unrecognised octicon: {name}") + except KeyError as exc: + raise KeyError(f"Unrecognised octicon: {name}") from exc match = HEIGHT_REGEX.match(height) if not match: @@ -85,7 +85,7 @@ def get_octicon( original_height = 16 if "16" not in data["heights"]: - original_height = int(list(data["heights"].keys())[0]) + original_height = int(next(iter(data["heights"].keys()))) elif "24" in data["heights"]: if height_unit == "px": if height_value >= 24: @@ -172,7 +172,7 @@ def run(self) -> List[nodes.Node]: return [list_node] -class fontawesome(nodes.Element, nodes.General): +class fontawesome(nodes.Element, nodes.General): # noqa: N801 """Node for rendering fontawesome icon.""" @@ -191,7 +191,7 @@ def run(self) -> Tuple[List[nodes.Node], List[nodes.system_message]]: icon, classes = self.text.split(";", 1) if ";" in self.text else [self.text, ""] icon = icon.strip() node = fontawesome( - icon=icon, classes=[self.style, f"fa-{icon}"] + classes.split() + icon=icon, classes=[self.style, f"fa-{icon}", *classes.split()] ) self.set_source_info(node) return [node], [] @@ -257,8 +257,8 @@ def get_material_icon( """ try: data = get_material_icon_data(style)[name] - except KeyError: - raise KeyError(f"Unrecognised material-{style} icon: {name}") + except KeyError as exc: + raise KeyError(f"Unrecognised material-{style} icon: {name}") from exc match = HEIGHT_REGEX.match(height) if not match: @@ -270,7 +270,7 @@ def get_material_icon( original_height = 20 if "20" not in data["heights"]: - original_height = int(list(data["heights"].keys())[0]) + original_height = int(next(iter(data["heights"].keys()))) elif "24" in data["heights"]: if height_unit == "px": if height_value >= 24: diff --git a/sphinx_design/tabs.py b/sphinx_design/tabs.py index a114170..96d0f73 100644 --- a/sphinx_design/tabs.py +++ b/sphinx_design/tabs.py @@ -34,7 +34,7 @@ def run(self) -> List[nodes.Node]: """Run the directive.""" self.assert_has_content() tab_set = create_component( - "tab-set", classes=["sd-tab-set"] + self.options.get("class", []) + "tab-set", classes=["sd-tab-set", *self.options.get("class", [])] ) self.set_source_info(tab_set) self.state.nested_parse(self.content, self.content_offset, tab_set) @@ -93,7 +93,7 @@ def run(self) -> List[nodes.Node]: ) tab_item = create_component( "tab-item", - classes=["sd-tab-item"] + self.options.get("class-container", []), + classes=["sd-tab-item", *self.options.get("class-container", [])], selected=("selected" in self.options), ) @@ -102,7 +102,7 @@ def run(self) -> List[nodes.Node]: tab_label = nodes.rubric( self.arguments[0], *textnodes, - classes=["sd-tab-label"] + self.options.get("class-label", []), + classes=["sd-tab-label", *self.options.get("class-label", [])], ) if "sync" in self.options: tab_label["sync_id"] = self.options["sync"] @@ -112,7 +112,7 @@ def run(self) -> List[nodes.Node]: # add tab content tab_content = create_component( "tab-content", - classes=["sd-tab-content"] + self.options.get("class-content", []), + classes=["sd-tab-content", *self.options.get("class-content", [])], ) self.state.nested_parse(self.content, self.content_offset, tab_content) tab_item += tab_content @@ -134,7 +134,7 @@ def run(self) -> List[nodes.Node]: """Run the directive.""" self.assert_has_content() tab_set = create_component( - "tab-set", classes=["sd-tab-set"] + self.options.get("class-set", []) + "tab-set", classes=["sd-tab-set", *self.options.get("class-set", [])] ) self.set_source_info(tab_set) self.state.nested_parse(self.content, self.content_offset, tab_set) @@ -153,30 +153,30 @@ def run(self) -> List[nodes.Node]: tab_label = nodes.rubric( language.upper(), nodes.Text(language.upper()), - classes=["sd-tab-label"] + self.options.get("class-label", []), + classes=["sd-tab-label", *self.options.get("class-label", [])], ) if "no-sync" not in self.options: tab_label["sync_id"] = f"tabcode-{language}" tab_content = create_component( "tab-content", children=[item], - classes=["sd-tab-content"] + self.options.get("class-content", []), + classes=["sd-tab-content", *self.options.get("class-content", [])], ) tab_item = create_component( "tab-item", children=[tab_label, tab_content], - classes=["sd-tab-item"] + self.options.get("class-item", []), + classes=["sd-tab-item", *self.options.get("class-item", [])], ) new_children.append(tab_item) tab_set.children = new_children return [tab_set] -class sd_tab_input(nodes.Element, nodes.General): +class sd_tab_input(nodes.Element, nodes.General): # noqa: N801 pass -class sd_tab_label(nodes.TextElement, nodes.General): +class sd_tab_label(nodes.TextElement, nodes.General): # noqa: N801 pass @@ -213,15 +213,13 @@ def run(self) -> None: # setup id generators tab_set_id_base = "sd-tab-set-" - tab_set_id_num = 0 tab_item_id_base = "sd-tab-item-" tab_item_id_num = 0 - for tab_set in findall(self.document)( - lambda node: is_component(node, "tab-set") + for tab_set_id_num, tab_set in enumerate( + findall(self.document)(lambda node: is_component(node, "tab-set")) ): tab_set_identity = tab_set_id_base + str(tab_set_id_num) - tab_set_id_num += 1 children = [] # get the first selected node selected_idx = None @@ -242,7 +240,6 @@ def run(self) -> None: try: tab_label, tab_content = tab_item.children except ValueError: - print(tab_item) raise tab_item_identity = tab_item_id_base + str(tab_item_id_num) tab_item_id_num += 1 diff --git a/tests/conftest.py b/tests/conftest.py index f7b924b..6b67058 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,9 +13,9 @@ if version_info >= (7, 2): # see https://github.com/sphinx-doc/sphinx/pull/11526 - from pathlib import Path as sphinx_path + from pathlib import Path as sphinx_path # noqa: N813 else: - from sphinx.testing.path import path as sphinx_path # type: ignore + from sphinx.testing.path import path as sphinx_path # type: ignore[no-redef] class SphinxBuilder: @@ -53,7 +53,7 @@ def get_doctree( self.app.env.apply_post_transforms(doctree, docname) # make source path consistent for test comparisons for node in findall(doctree)(include_self=True): - if not ("source" in node and node["source"]): + if not (node.get("source")): continue node["source"] = Path(node["source"]).relative_to(self.src_path).as_posix() if node["source"].endswith(".rst"): @@ -82,7 +82,8 @@ def _create_project( ) src_path.joinpath("conf.py").write_text(content, encoding="utf8") app = make_app( - srcdir=sphinx_path(os.path.abspath(str(src_path))), buildername=buildername + srcdir=sphinx_path(os.path.abspath(str(src_path))), # noqa: PTH100 + buildername=buildername, ) return SphinxBuilder(app, src_path) diff --git a/tests/test_snippets.py b/tests/test_snippets.py index ee9e280..987b727 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -1,4 +1,5 @@ """Test the documented snippets run correctly, and are the same for both RST and MyST.""" + from pathlib import Path from typing import Callable diff --git a/tox.ini b/tox.ini index f5c2f88..cd3994a 100644 --- a/tox.ini +++ b/tox.ini @@ -66,7 +66,3 @@ commands = --re-ignore docs/_build/.* \ --port 0 --open-browser \ -n -b {posargs:html} docs/ docs/_build/{posargs:html} - -[flake8] -max-line-length = 100 -extend-ignore = E203 From e2c5c6e712b5482df76ea53a574f33f0f222b6d0 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Sat, 18 May 2024 15:26:57 +0200 Subject: [PATCH 2/3] update --- pyproject.toml | 1 + tests/conftest.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6df46b5..c5ed5d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ testing = [ "pytest~=7.1", "pytest-cov", "pytest-regressions", + "defusedxml", ] theme_furo = ["furo~=2023.7.0"] theme_pydata = ["pydata-sphinx-theme~=0.13.0"] diff --git a/tests/conftest.py b/tests/conftest.py index 6b67058..abc06f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,7 +53,7 @@ def get_doctree( self.app.env.apply_post_transforms(doctree, docname) # make source path consistent for test comparisons for node in findall(doctree)(include_self=True): - if not (node.get("source")): + if not (hasattr(node, "get") and node.get("source")): continue node["source"] = Path(node["source"]).relative_to(self.src_path).as_posix() if node["source"].endswith(".rst"): From 93619497658fc6d5525a960685ecc871bc4394f5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Sat, 18 May 2024 15:30:41 +0200 Subject: [PATCH 3/3] Update ci.yml --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bb04f3..8f4a939 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,7 @@ jobs: if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: + token: ${{ secrets.CODECOV_TOKEN }} name: pytests flags: pytests file: ./coverage.xml