diff --git a/.cruft.json b/.cruft.json index af428c9..12ded48 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "https://github.com/iterative/py-template", - "commit": "0aac03d10cbc7b2f7e144e728de5043b7a3458cb", + "commit": "15ee26df315020399731c6291d61bef81a3fc5d3", "context": { "cookiecutter": { "project_name": "dvc-render", diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2501f9..98027b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,8 @@ default_language_version: python: python3 repos: - - repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -23,32 +19,14 @@ repos: args: ['--fix=lf'] - id: sort-simple-yaml - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.1.7' + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell additional_dependencies: ["tomli"] - - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 - hooks: - - id: pyupgrade - args: [--py38-plus] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==23.7.10 - - flake8-comprehensions==3.14.0 - - flake8-debugger==4.1.2 - - flake8-string-format==0.3.0 - - repo: https://github.com/pycqa/bandit - rev: 1.7.5 - hooks: - - id: bandit - args: [-c, pyproject.toml] - additional_dependencies: [".[toml]"] diff --git a/noxfile.py b/noxfile.py index ff610f4..1a31413 100644 --- a/noxfile.py +++ b/noxfile.py @@ -35,7 +35,6 @@ def lint(session: nox.Session) -> None: args = *(session.posargs or ("--show-diff-on-failure",)), "--all-files" session.run("pre-commit", "run", *args) session.run("python", "-m", "mypy") - session.run("python", "-m", "pylint", *locations) @nox.session diff --git a/pyproject.toml b/pyproject.toml index 79b1361..b9f5bf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,28 +4,63 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] -[tool.black] -line-length = 88 -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist -)/ -''' - -[tool.isort] -profile = "black" -known_first_party = ["dvc_render"] -line_length = 88 +[project] +name = "dvc-render" +description = "Dvc Render" +readme = "README.rst" +license = {text = "Apache-2.0"} +authors = [{ name = "Iterative", email = "support@dvc.org" }] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Development Status :: 4 - Beta", +] +requires-python = ">=3.8" +dynamic = ["version"] +dependencies = [] + +[project.urls] +Issues = "https://github.com/iterative/dvc-render/issues" +Source = "https://github.com/iterative/dvc-render" + +[project.optional-dependencies] +table = [ + "tabulate>=0.8.7", + "flatten_dict<1,>=0.4.1", +] +markdown = [ + "dvc-render[table]", + "matplotlib", +] +docs = [ + "mkdocs==1.5.2", + "mkdocs-gen-files==0.5.0", + "mkdocs-material==9.3.1", + "mkdocs-section-index==0.3.6", + "mkdocstrings-python==1.6.3", +] +tests = [ + "dvc-render[table]", + "dvc-render[markdown]", + "pytest==7.2.0", + "pytest-sugar==0.9.5", + "pytest-cov==3.0.0", + "pytest-mock==3.8.2", + "mypy==1.2.0", +] +dev = [ + "dvc-render[table]", + "dvc-render[markdown]", + "dvc-render[tests]", + "dvc-render[docs]", +] + +[tool.setuptools.packages.find] +where = ["src"] +namespaces = false [tool.pytest.ini_options] addopts = "-ra" @@ -70,27 +105,38 @@ module = [ ] ignore_missing_imports = true +[tool.codespell] +ignore-words-list = " " -[tool.pylint.format] -max-line-length = 88 - -[tool.pylint.message_control] -enable = ["c-extension-no-member", "no-else-return"] -disable = [ - "missing-module-docstring", - "missing-class-docstring", - "invalid-name", - "R0801", - "C0415" +[tool.ruff] +ignore = [ + "S101", # assert + "PLR2004", # magic-value-comparison + "PLW2901", # redefined-loop-name + "ISC001", # single-line-implicit-string-concatenation + "SIM105", # suppressible-exception + "SIM108", # if-else-block-instead-of-if-exp + "D203", # one blank line before class + "D213", # multi-line-summary-second-line + "RUF012", # mutable class attributes + "PT007", # value types in pytest.mark.parametrize ] +select = [ + "F", "E", "W", "C90", "I", "N", "UP", "YTT", "ASYNC", "S", "BLE", "B", "A", "C4", "T10", + "EXE", "ISC", "ICN", "G", "INP", "PIE", "T20", "PYI", "PT", "Q", "RSE", "RET", + "SLOT", "SIM", "TID", "TCH", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY", + "FLY", "PERF101", "RUF", +] +show-source = true +show-fixes = true -[tool.pylint.variables] -dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" -ignored-argument-names = "_.*|^ignored_|^unused_|args|kwargs" +[tool.ruff.per-file-ignores] +"noxfile.py" = ["D", "PTH"] +"tests/**" = ["S", "ARG001", "ARG002", "ANN"] +"docs/**" = ["INP"] -[tool.codespell] -ignore-words-list = " " +[tool.ruff.lint.flake8-type-checking] +strict = true -[tool.bandit] -exclude_dirs = ["tests"] -skips = ["B101"] +[tool.ruff.lint.isort] +known-first-party = ["{{ cookiecutter.package_name }}"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a95590a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,78 +0,0 @@ -[metadata] -description = DVC render -name = dvc-render -long_description = file: README.rst -long_description_content_type = text/x-rst -license = Apache-2.0 -license_file = LICENSE -url = https://github.com/iterative/dvc-render -platforms=any -authors = Iterative -maintainer_email = support@dvc.org -classifiers = - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Development Status :: 4 - Beta - -[options] -python_requires = >=3.8 -zip_safe = False -package_dir= - =src -packages = find: - -[options.extras_require] -table = - tabulate>=0.8.7 - flatten_dict<1,>=0.4.1 -markdown = - %(table)s - matplotlib -docs = - mkdocs==1.5.2 - mkdocs-gen-files==0.5.0 - mkdocs-material==9.3.1 - mkdocs-section-index==0.3.6 - mkdocstrings-python==1.6.3 -tests = - %(table)s - %(markdown)s - funcy>=1.17 - pytest==7.2.0 - pytest-sugar>=0.9.6,<1.0 - pytest-cov==3.0.0 - pytest-mock==3.8.2 - pylint==2.15.0 - mypy==0.981 - pytest-test-utils>=0.0.6 -dev = - %(table)s - %(markdown)s - %(tests)s - %(docs)s - -[options.packages.find] -exclude = - tests - tests.* -where=src - -[flake8] -ignore= - # Whitespace before ':' - E203 - # Too many leading '#' for block comment - E266 - # Line break occurred before a binary operator - W503 - # unindexed parameters in the str.format, see: - # https://pypi.org/project/flake8-string-format/ - P1 -max_line_length = 88 -max-complexity = 15 -select = B,C,E,F,W,T4,B902,T,P -show_source = true -count = true diff --git a/src/dvc_render/base.py b/src/dvc_render/base.py index d374ac0..8b47826 100644 --- a/src/dvc_render/base.py +++ b/src/dvc_render/base.py @@ -19,7 +19,12 @@ class Renderer(abc.ABC): EXTENSIONS: Iterable[str] = {} - def __init__(self, datapoints: List = None, name: str = None, **properties): + def __init__( + self, + datapoints: Optional[List] = None, + name: Optional[str] = None, + **properties, + ): self.datapoints = datapoints or [] self.name = name or "" self.properties = properties @@ -34,12 +39,12 @@ def partial_html(self, **kwargs) -> str: @property @abc.abstractmethod - def TYPE(self): # pylint: disable=missing-function-docstring + def TYPE(self): # noqa: N802 raise NotImplementedError @property @abc.abstractmethod - def SCRIPTS(self): # pylint: disable=missing-function-docstring + def SCRIPTS(self): # noqa: N802 raise NotImplementedError @staticmethod @@ -58,13 +63,11 @@ def generate_html(self, html_path=None) -> str: return self.DIV.format(id=div_id, partial=partial) return "" - def generate_markdown( - self, report_path: Optional[StrPath] = None - ) -> str: # pylint: disable=missing-function-docstring + def generate_markdown(self, report_path: Optional[StrPath] = None) -> str: # pylint: disable=missing-function-docstring "Generate a markdown element" raise NotImplementedError @classmethod - def matches(cls, filename, properties) -> bool: # pylint: disable=unused-argument + def matches(cls, filename) -> bool: # pylint: disable=unused-argument "Check if the Renderer is suitable." return Path(filename).suffix in cls.EXTENSIONS diff --git a/src/dvc_render/exceptions.py b/src/dvc_render/exceptions.py index 9d6e90e..6986f8e 100644 --- a/src/dvc_render/exceptions.py +++ b/src/dvc_render/exceptions.py @@ -1,7 +1,7 @@ -class DvcRenderException(Exception): +class DvcRenderError(Exception): pass -class MissingPlaceholderError(DvcRenderException): +class MissingPlaceholderError(DvcRenderError): def __init__(self, placeholder, template_type): super().__init__(f"{template_type} template has to contain '{placeholder}'.") diff --git a/src/dvc_render/html.py b/src/dvc_render/html.py index b08b377..c06e716 100644 --- a/src/dvc_render/html.py +++ b/src/dvc_render/html.py @@ -4,7 +4,7 @@ from dvc_render.image import ImageRenderer -from .exceptions import DvcRenderException +from .exceptions import DvcRenderError if TYPE_CHECKING: from .base import Renderer, StrPath @@ -28,7 +28,7 @@ """ -class MissingPlaceholderError(DvcRenderException): +class MissingPlaceholderError(DvcRenderError): def __init__(self, placeholder): super().__init__(f"HTML template has to contain '{placeholder}'.") diff --git a/src/dvc_render/image.py b/src/dvc_render/image.py index 60b832f..e597266 100644 --- a/src/dvc_render/image.py +++ b/src/dvc_render/image.py @@ -22,7 +22,7 @@ class ImageRenderer(Renderer): EXTENSIONS = {".jpg", ".jpeg", ".gif", ".png", ".svg"} - def partial_html(self, html_path=None, **kwargs) -> str: + def partial_html(self, html_path=None, **kwargs) -> str: # noqa: ARG002 div_content = [] for datapoint in self.datapoints: src = datapoint[self.SRC_FIELD] @@ -50,7 +50,7 @@ def partial_html(self, html_path=None, **kwargs) -> str: return "\n".join(div_content) return "" - def generate_markdown(self, report_path=None) -> str: + def generate_markdown(self) -> str: # type: ignore[override] content = [] for datapoint in self.datapoints: src = datapoint[self.SRC_FIELD] diff --git a/src/dvc_render/plotly.py b/src/dvc_render/plotly.py index b036089..626bd0b 100644 --- a/src/dvc_render/plotly.py +++ b/src/dvc_render/plotly.py @@ -42,7 +42,7 @@ def __init__( self.name = name self.fill_value = fill_value - def partial_html(self, **kwargs) -> str: + def partial_html(self, **kwargs) -> str: # noqa: ARG002 return json.dumps(self._get_plotly_data()) def _get_plotly_data(self): diff --git a/src/dvc_render/table.py b/src/dvc_render/table.py index 90eb626..8ec01a8 100644 --- a/src/dvc_render/table.py +++ b/src/dvc_render/table.py @@ -27,13 +27,13 @@ class TableRenderer(Renderer): def to_tabulate(cls, datapoints, tablefmt): """Convert datapoints to tabulate format""" if tabulate is None: - raise ImportError(f"{cls.__name__} requires `tabulate`.") + raise ImportError(f"{cls.__name__} requires `tabulate`.") # noqa: TRY003 data = list_dict_to_dict_list(datapoints) return tabulate(data, headers="keys", tablefmt=tablefmt) - def partial_html(self, **kwargs) -> str: + def partial_html(self, **kwargs) -> str: # noqa: ARG002 return self.to_tabulate(self.datapoints, tablefmt="html") - def generate_markdown(self, report_path=None) -> str: + def generate_markdown(self) -> str: # type: ignore[override] table = self.to_tabulate(self.datapoints, tablefmt="github") return f"\n{self.name}\n\n{table}" diff --git a/src/dvc_render/utils.py b/src/dvc_render/utils.py index 31667b2..c8f6d05 100644 --- a/src/dvc_render/utils.py +++ b/src/dvc_render/utils.py @@ -1,4 +1,4 @@ -from flatten_dict import flatten # type: ignore +from flatten_dict import flatten # type: ignore[import] def list_dict_to_dict_list(list_dict): diff --git a/src/dvc_render/vega.py b/src/dvc_render/vega.py index 9a62bc6..8e5a487 100644 --- a/src/dvc_render/vega.py +++ b/src/dvc_render/vega.py @@ -91,7 +91,7 @@ def __init__(self, datapoints: List, name: str, **properties): self._split_content: Dict[str, str] = {} - def get_filled_template( + def get_filled_template( # noqa: C901 self, split_anchors: Optional[List[str]] = None, strict: bool = True, @@ -134,7 +134,7 @@ def get_filled_template( if name == "data": if not self.template.has_anchor(name): anchor = self.template.anchor(name) - raise BadTemplateError( + raise BadTemplateError( # noqa: TRY003 f"Template '{self.template.name}' " f"is not using '{anchor}' anchor" ) @@ -160,18 +160,18 @@ def get_template(self): """ return self.template.content - def partial_html(self, **kwargs) -> str: + def partial_html(self, **kwargs) -> str: # noqa: ARG002 content = self.get_filled_template() return json.dumps(content) def generate_markdown(self, report_path=None) -> str: if not isinstance(self.template, LinearTemplate): - warn("`generate_markdown` can only be used with `LinearTemplate`") + warn("`generate_markdown` can only be used with `LinearTemplate`") # noqa: B028 return "" try: from matplotlib import pyplot as plt except ImportError as e: - raise ImportError("matplotlib is required for `generate_markdown`") from e + raise ImportError("matplotlib is required for `generate_markdown`") from e # noqa: TRY003 data = list_dict_to_dict_list(self.datapoints) if data: @@ -181,7 +181,7 @@ def generate_markdown(self, report_path=None) -> str: output_file = output_file.with_suffix(".png") output_file.parent.mkdir(exist_ok=True, parents=True) else: - output_file = io.BytesIO() # type: ignore + output_file = io.BytesIO() # type: ignore[assignment] x = self.properties.get("x") y = self.properties.get("y") @@ -200,9 +200,7 @@ def generate_markdown(self, report_path=None) -> str: if report_path: return f"\n![{self.name}]({output_file.relative_to(report_folder)})" - base64_str = base64.b64encode( - output_file.getvalue() # type: ignore - ).decode() + base64_str = base64.b64encode(output_file.getvalue()).decode() # type: ignore[attr-defined] src = f"data:image/png;base64,{base64_str}" return f"\n![{self.name}]({src})" @@ -411,7 +409,7 @@ def _get_domain(self, varied_keys: List[str], varied_values: Dict[str, set]): domain.sort() return domain - def _fill_optional_anchor_mapping( + def _fill_optional_anchor_mapping( # noqa: PLR0913 self, split_anchors: List[str], optional_anchors: List[str], diff --git a/src/dvc_render/vega_templates.py b/src/dvc_render/vega_templates.py index 0f6dd05..2ab708d 100644 --- a/src/dvc_render/vega_templates.py +++ b/src/dvc_render/vega_templates.py @@ -3,23 +3,23 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from .exceptions import DvcRenderException +from .exceptions import DvcRenderError if TYPE_CHECKING: from .base import StrPath -class TemplateNotFoundError(DvcRenderException): +class TemplateNotFoundError(DvcRenderError): def __init__(self, path): super().__init__(f"Template '{path}' not found.") -class NoFieldInDataError(DvcRenderException): +class NoFieldInDataError(DvcRenderError): def __init__(self, field_name): super().__init__(f"Field '{field_name}' does not exist in provided data.") -class TemplateContentDoesNotMatch(DvcRenderException): +class TemplateContentDoesNotMatchError(DvcRenderError): def __init__(self, template_name: str, path: str): super().__init__( f"Template '{path}' already exists " @@ -28,7 +28,7 @@ def __init__(self, template_name: str, path: str): ) -class BadTemplateError(DvcRenderException): +class BadTemplateError(DvcRenderError): pass @@ -56,9 +56,8 @@ def list_replace_value(l: list, name: str, value: str) -> list: # noqa: E741 e = list_replace_value(e, name, value) elif isinstance(e, dict): e = dict_replace_value(e, name, value) - elif isinstance(e, str): - if e == name: - e = value + elif isinstance(e, str) and e == name: + e = value x.append(e) return x @@ -66,18 +65,14 @@ def list_replace_value(l: list, name: str, value: str) -> list: # noqa: E741 def find_value(d: Union[dict, list, str], value: str) -> bool: if isinstance(d, dict): for v in d.values(): - if isinstance(v, dict): - if find_value(v, value): - return True - if isinstance(v, str): - if v == value: - return True - if isinstance(v, list): - if any(find_value(e, value) for e in v): - return True - elif isinstance(d, str): - if d == value: - return True + if isinstance(v, dict) and find_value(v, value): + return True + if isinstance(v, str) and v == value: + return True + if isinstance(v, list) and any(find_value(e, value) for e in v): + return True + elif isinstance(d, str) and d == value: + return True return False @@ -97,7 +92,7 @@ def __init__( or self.DEFAULT_CONTENT and not isinstance(self.DEFAULT_CONTENT, dict) ): - raise BadTemplateError() + raise BadTemplateError self._original_content = content or self.DEFAULT_CONTENT self.content: Dict[str, Any] = self._original_content self.name = name or self.DEFAULT_NAME @@ -127,8 +122,7 @@ def reset(self): def has_anchor(self, name) -> bool: "Check if ANCHOR formatted with name is in content." - found = find_value(self.content, self.anchor(name)) - return found + return find_value(self.content, self.anchor(name)) def fill_anchor(self, name, value) -> None: "Replace anchor `name` with `value` in content." @@ -731,7 +725,7 @@ def get_template( return Template(content, name=template) for template_cls in TEMPLATES: - if template_cls.DEFAULT_NAME == template: + if template == template_cls.DEFAULT_NAME: return template_cls() raise TemplateNotFoundError(template) @@ -756,6 +750,6 @@ def dump_templates(output: "StrPath", targets: Optional[List] = None) -> None: if path.exists(): content = path.read_text(encoding="utf-8") if content != template.content: - raise TemplateContentDoesNotMatch(template.DEFAULT_NAME, str(path)) + raise TemplateContentDoesNotMatchError(template.DEFAULT_NAME, str(path)) else: path.write_text(json.dumps(template.content), encoding="utf-8") diff --git a/tests/test_html.py b/tests/test_html.py index ae239a6..97fb08b 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -2,7 +2,6 @@ import os import pytest - from dvc_render.html import ( HTML, PAGE_HTML, @@ -46,7 +45,7 @@ @pytest.mark.parametrize( - "template,page_elements,expected_page", + ("template", "page_elements", "expected_page"), [ ( None, @@ -76,8 +75,8 @@ def test_html(template, page_elements, expected_page): assert result == expected_page -def test_render_html_with_custom_template(mocker, tmp_dir): - output_file = tmp_dir / "output_file" +def test_render_html_with_custom_template(mocker, tmp_path): + output_file = tmp_path / "output_file" render_html(mocker.MagicMock(), output_file) assert output_file.read_text() == PAGE_HTML.replace("{plot_divs}", "").replace( @@ -87,7 +86,7 @@ def test_render_html_with_custom_template(mocker, tmp_dir): render_html(mocker.MagicMock(), output_file, CUSTOM_PAGE_HTML) assert output_file.read_text() == CUSTOM_PAGE_HTML.format(plot_divs="") - custom_template = tmp_dir / "custom_template" + custom_template = tmp_path / "custom_template" custom_template.write_text(CUSTOM_PAGE_HTML) render_html(mocker.MagicMock(), output_file, custom_template) assert output_file.read_text() == CUSTOM_PAGE_HTML.format(plot_divs="") diff --git a/tests/test_image.py b/tests/test_image.py index 07ab3a2..db54821 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,14 +1,13 @@ import os import pytest - from dvc_render.image import ImageRenderer # pylint: disable=missing-function-docstring @pytest.mark.parametrize( - "extension, matches", + ("extension", "matches"), ( (".csv", False), (".json", False), @@ -23,7 +22,7 @@ ) def test_matches(extension, matches): filename = "file" + extension - assert ImageRenderer.matches(filename, {}) == matches + assert ImageRenderer.matches(filename) == matches @pytest.mark.parametrize("html_path", [None, "/output/dir/index.html"]) @@ -69,7 +68,7 @@ def test_invalid_generate_markdown(): @pytest.mark.parametrize( - "html_path,img_path,expected_path", + ("html_path", "img_path", "expected_path"), [ ( os.path.join("output", "path", "index.html"), @@ -83,9 +82,9 @@ def test_invalid_generate_markdown(): ), ], ) -def test_render_evaluate_path(tmp_dir, html_path, img_path, expected_path): - abs_html_path = tmp_dir / html_path - abs_img_path = tmp_dir / img_path +def test_render_evaluate_path(tmp_path, html_path, img_path, expected_path): + abs_html_path = tmp_path / html_path + abs_img_path = tmp_path / img_path datapoints = [ { diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 631c844..8f72df8 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,5 +1,4 @@ import pytest - from dvc_render.markdown import ( PAGE_MARKDOWN, Markdown, @@ -17,7 +16,7 @@ @pytest.mark.parametrize( - "template,page_elements,expected_page", + ("template", "page_elements", "expected_page"), [ ( None, @@ -47,8 +46,8 @@ def test_no_placeholder(): Markdown(template) -def test_render_markdown_to_file(tmp_dir): - output_file = tmp_dir / "report" +def test_render_markdown_to_file(tmp_path): + output_file = tmp_path / "report" assert output_file == render_markdown([], output_file) diff --git a/tests/test_parallel_coordinates.py b/tests/test_parallel_coordinates.py index 2e0a46a..ca96de5 100644 --- a/tests/test_parallel_coordinates.py +++ b/tests/test_parallel_coordinates.py @@ -128,14 +128,14 @@ def test_color_by_categorical(): } -def test_write_parallel_coordinates(tmp_dir): +def test_write_parallel_coordinates(tmp_path): datapoints = [ {"categorical": "foo", "scalar": "0.1"}, {"categorical": "bar", "scalar": "2"}, ] renderer = ParallelCoordinatesRenderer(datapoints) - html_path = render_html(renderers=[renderer], output_file=tmp_dir / "index.html") + html_path = render_html(renderers=[renderer], output_file=tmp_path / "index.html") html_text = html_path.read_text() diff --git a/tests/test_templates.py b/tests/test_templates.py index 5c826e6..41e8aec 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -2,13 +2,12 @@ import os import pytest - from dvc_render.vega_templates import ( TEMPLATES, LinearTemplate, ScatterTemplate, Template, - TemplateContentDoesNotMatch, + TemplateContentDoesNotMatchError, TemplateNotFoundError, dump_templates, find_value, @@ -24,7 +23,7 @@ def test_raise_on_no_template(): @pytest.mark.parametrize( - "template_path, target_name", + ("template_path", "target_name"), [ (os.path.join(".dvc", "plots", "template.json"), "template"), (os.path.join(".dvc", "plots", "template.json"), "template.json"), @@ -39,32 +38,41 @@ def test_raise_on_no_template(): ("template.json", "template.json"), ], ) -def test_get_template_from_dir(tmp_dir, template_path, target_name): +def test_get_template_from_dir(tmp_path, monkeypatch, template_path, target_name): + monkeypatch.chdir(tmp_path) template_content = {"template_content": "foo"} - tmp_dir.gen(template_path, json.dumps(template_content)) + template_path = tmp_path / template_path + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text(json.dumps(template_content), encoding="utf-8") assert get_template(target_name, ".dvc/plots").content == template_content -def test_get_template_exact_match(tmp_dir): - tmp_dir.gen(os.path.join("foodir", "bar_template.json"), "bar") +def test_get_template_exact_match(tmp_path): + template_path = tmp_path / "foodir" / "bar_template.json" + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text("bar", encoding="utf-8") with pytest.raises(TemplateNotFoundError): # This was unexpectedly working when using rglob({template_name}*) # and could cause bugs. get_template("bar", "foodir") -def test_get_template_from_file(tmp_dir): +def test_get_template_from_file(tmp_path): template_content = {"template_content": "foo"} - tmp_dir.gen("foo/bar.json", json.dumps(template_content)) - assert get_template("foo/bar.json").content == template_content + template_path = tmp_path / "foo/bar.json" + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text(json.dumps(template_content), encoding="utf-8") + assert get_template(template_path).content == template_content -def test_get_template_fs(tmp_dir, mocker): +def test_get_template_fs(tmp_path, mocker): template_content = {"template_content": "foo"} - tmp_dir.gen("foo/bar.json", json.dumps(template_content)) + template_path = tmp_path / "foo/bar.json" + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text(json.dumps(template_content), encoding="utf-8") fs = mocker.MagicMock() mocker.patch("json.load", return_value={}) - get_template("foo/bar.json", fs=fs) + get_template(template_path, fs=fs) fs.open.assert_called() fs.exists.assert_called() @@ -74,14 +82,14 @@ def test_get_default_template(): @pytest.mark.parametrize( - "targets,expected_templates", + ("targets", "expected_templates"), ( ([None, TEMPLATES]), (["linear", "scatter"], [ScatterTemplate, LinearTemplate]), ), ) -def test_init(tmp_dir, targets, expected_templates): - output = "plots" +def test_init(tmp_path, targets, expected_templates): + output = tmp_path / "plots" dump_templates(output, targets) assert set(os.listdir(output)) == { @@ -89,14 +97,14 @@ def test_init(tmp_dir, targets, expected_templates): } -def test_raise_on_init_modified(tmp_dir): - dump_templates(output=".", targets=["linear"]) +def test_raise_on_init_modified(tmp_path): + dump_templates(output=tmp_path, targets=["linear"]) - with open(tmp_dir / "linear.json", "a", encoding="utf-8") as fd: + with open(tmp_path / "linear.json", "a", encoding="utf-8") as fd: fd.write("modification") - with pytest.raises(TemplateContentDoesNotMatch): - dump_templates(output=".", targets=["linear"]) + with pytest.raises(TemplateContentDoesNotMatchError): + dump_templates(output=tmp_path, targets=["linear"]) def test_escape_special_characters(): @@ -105,7 +113,7 @@ def test_escape_special_characters(): @pytest.mark.parametrize( - "content_dict, value_name", + ("content_dict", "value_name"), [ ({"key": "value"}, "value"), ({"key": {"subkey": "value"}}, "value"), diff --git a/tests/test_vega.py b/tests/test_vega.py index 482c1bb..4bbf5f8 100644 --- a/tests/test_vega.py +++ b/tests/test_vega.py @@ -1,8 +1,8 @@ import json +import os from typing import Any, Dict, List import pytest - from dvc_render.vega import OPTIONAL_ANCHOR_RANGES, BadTemplateError, VegaRenderer from dvc_render.vega_templates import NoFieldInDataError, Template @@ -10,7 +10,7 @@ @pytest.mark.parametrize( - "extension, matches", + ("extension", "matches"), ( (".csv", True), (".json", True), @@ -24,7 +24,7 @@ ), ) def test_matches(extension, matches): - assert VegaRenderer.matches("file" + extension, {}) == matches + assert VegaRenderer.matches("file" + extension) == matches def test_init_empty(): @@ -104,7 +104,7 @@ def test_bad_template_on_init(): @pytest.mark.parametrize( - "bad_content,good_content", + ("bad_content", "good_content"), ( ( {"data": {"values": "BAD_ANCHOR"}}, @@ -123,16 +123,18 @@ def test_bad_template_on_init(): ), ), ) -def test_bad_template_on_missing_data(tmp_dir, bad_content, good_content): - tmp_dir.gen("bar.json", json.dumps(bad_content)) +def test_bad_template_on_missing_data(tmp_path, bad_content, good_content): + template_path = tmp_path / "bar.json" + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text(json.dumps(bad_content), encoding="utf-8") datapoints = [{"val": 2}, {"val": 3}] - renderer = VegaRenderer(datapoints, "foo", template="bar.json") + renderer = VegaRenderer(datapoints, "foo", template=template_path) with pytest.raises(BadTemplateError): renderer.get_filled_template() - tmp_dir.gen("bar.json", json.dumps(good_content)) - renderer = VegaRenderer(datapoints, "foo", template="bar.json") + template_path.write_text(json.dumps(good_content), encoding="utf-8") + renderer = VegaRenderer(datapoints, "foo", template=template_path) assert renderer.get_filled_template() @@ -147,15 +149,15 @@ def test_raise_on_wrong_field(): @pytest.mark.parametrize("name", ["foo", "foo/bar", "foo/bar.tsv"]) @pytest.mark.parametrize("to_file", [True, False]) -def test_generate_markdown(tmp_dir, mocker, name, to_file): +def test_generate_markdown(tmp_path, mocker, name, to_file): # pylint: disable-msg=too-many-locals - import matplotlib.pyplot + import matplotlib.pyplot as plt - plot = mocker.spy(matplotlib.pyplot, "plot") - title = mocker.spy(matplotlib.pyplot, "title") - xlabel = mocker.spy(matplotlib.pyplot, "xlabel") - ylabel = mocker.spy(matplotlib.pyplot, "ylabel") - savefig = mocker.spy(matplotlib.pyplot, "savefig") + plot = mocker.spy(plt, "plot") + title = mocker.spy(plt, "title") + xlabel = mocker.spy(plt, "xlabel") + ylabel = mocker.spy(plt, "ylabel") + savefig = mocker.spy(plt, "savefig") props = {"x": "first_val", "y": "second_val", "title": "FOO"} datapoints = [ @@ -165,10 +167,10 @@ def test_generate_markdown(tmp_dir, mocker, name, to_file): renderer = VegaRenderer(datapoints, name, **props) if to_file: - report_folder = tmp_dir / "output" + report_folder = tmp_path / "output" report_folder.mkdir() - md = renderer.generate_markdown(tmp_dir / "output" / "report.md") - output_file = (tmp_dir / "output" / renderer.name).with_suffix(".png") + md = renderer.generate_markdown(tmp_path / "output" / "report.md") + output_file = (tmp_path / "output" / renderer.name).with_suffix(".png") assert output_file.exists() savefig.assert_called_with(output_file) assert f"![{name}]({output_file.relative_to(report_folder)})" in md @@ -224,36 +226,32 @@ def test_escape_special_characters(): assert filled["encoding"]["y"]["title"] == "foo.bar[1]" -def test_fill_anchor_in_string(tmp_dir): +def test_fill_anchor_in_string(tmp_path): y = "lab" x = "SR" - tmp_dir.gen( - "custom.json", - json.dumps( + template_content = { + "data": {"values": Template.anchor("data")}, + "transform": [ + {"joinaggregate": [{"op": "mean", "field": "lab", "as": "mean_y"}]}, { - "data": {"values": Template.anchor("data")}, - "transform": [ - {"joinaggregate": [{"op": "mean", "field": "lab", "as": "mean_y"}]}, - { - "calculate": "pow(" - + "datum. - datum.,2" - + ")", - "as": "SR", - }, - {"joinaggregate": [{"op": "sum", "field": "SR", "as": "SSR"}]}, - ], - "encoding": { - "x": {"field": Template.anchor("x")}, - "y": {"field": Template.anchor("y")}, - }, + "calculate": "pow(datum. - " "datum.,2)", + "as": "SR", }, - ), - ) + {"joinaggregate": [{"op": "sum", "field": "SR", "as": "SSR"}]}, + ], + "encoding": { + "x": {"field": Template.anchor("x")}, + "y": {"field": Template.anchor("y")}, + }, + } + template_path = tmp_path / "custom.json" + os.makedirs(template_path.parent, exist_ok=True) + template_path.write_text(json.dumps(template_content), encoding="utf-8") datapoints = [ {x: "B", y: "A"}, {x: "A", y: "A"}, ] - props = {"template": "custom.json", "x": x, "y": y} + props = {"template": template_path, "x": x, "y": y} renderer = VegaRenderer(datapoints, "foo", **props) filled = renderer.get_filled_template() @@ -263,16 +261,14 @@ def test_fill_anchor_in_string(tmp_dir): @pytest.mark.parametrize( - ",".join( - [ - "anchors_y_definitions", - "datapoints", - "y", - "expected_dp_keys", - "stroke_dash_encoding", - "pivot_field", - "group_by", - ] + ( + "anchors_y_definitions", + "datapoints", + "y", + "expected_dp_keys", + "stroke_dash_encoding", + "pivot_field", + "group_by", ), ( pytest.param( @@ -498,7 +494,7 @@ def test_fill_anchor_in_string(tmp_dir): ), ), ) -def test_optional_anchors_linear( +def test_optional_anchors_linear( # noqa: PLR0913 anchors_y_definitions, datapoints, y, @@ -531,16 +527,14 @@ def test_optional_anchors_linear( @pytest.mark.parametrize( - ",".join( - [ - "anchors_y_definitions", - "datapoints", - "y", - "expected_dp_keys", - "row_encoding", - "group_by_y", - "group_by_x", - ] + ( + "anchors_y_definitions", + "datapoints", + "y", + "expected_dp_keys", + "row_encoding", + "group_by_y", + "group_by_x", ), ( pytest.param( @@ -676,7 +670,7 @@ def test_optional_anchors_linear( ), ), ) -def test_optional_anchors_confusion( +def test_optional_anchors_confusion( # noqa: PLR0913 anchors_y_definitions, datapoints, y, @@ -708,15 +702,13 @@ def test_optional_anchors_confusion( @pytest.mark.parametrize( - ",".join( - [ - "anchors_y_definitions", - "datapoints", - "y", - "expected_dp_keys", - "shape_encoding", - "tooltip_encoding", - ] + ( + "anchors_y_definitions", + "datapoints", + "y", + "expected_dp_keys", + "shape_encoding", + "tooltip_encoding", ), ( pytest.param( @@ -870,7 +862,7 @@ def test_optional_anchors_confusion( ), ), ) -def test_optional_anchors_scatter( +def test_optional_anchors_scatter( # noqa: PLR0913 anchors_y_definitions, datapoints, y, @@ -908,12 +900,7 @@ def test_optional_anchors_scatter( @pytest.mark.parametrize( - ",".join( - [ - "revs", - "datapoints", - ] - ), + ("revs", "datapoints"), ( pytest.param( ["B"], @@ -1067,14 +1054,12 @@ def test_color_anchor(revs, datapoints): @pytest.mark.parametrize( - ",".join( - [ - "anchors_y_definitions", - "datapoints", - "y", - "expected_dp_keys", - "stroke_dash_encoding", - ] + ( + "anchors_y_definitions", + "datapoints", + "y", + "expected_dp_keys", + "stroke_dash_encoding", ), ( pytest.param(