From a78db016958741c114502fe89e94a1a75215aeb9 Mon Sep 17 00:00:00 2001 From: Guillaume Mulocher Date: Fri, 27 Oct 2023 15:00:42 +0200 Subject: [PATCH] refactor(anta): Clean reporter typing (#438) * Refactor(anta): Remove ColorManager class * Refactor: Remove ListResult * Refactor: Separate functions to render get_results * Test: Make python 3.8 happy again * Doc: Remove ColorManager from doc * Doc: Remove ListResult from doc --- anta/__init__.py | 2 + anta/cli/nrfu/utils.py | 13 ++-- anta/reporter/__init__.py | 77 +++++++++++++++------- anta/reporter/models.py | 39 ----------- anta/result_manager/__init__.py | 55 +++++----------- anta/result_manager/models.py | 37 +---------- anta/tools/pydantic.py | 5 +- docs/api/report_manager_models.md | 7 -- docs/api/result_manager_models.md | 4 -- tests/lib/fixture.py | 22 ++++--- tests/units/reporter/test__init__.py | 25 +++---- tests/units/result_manager/test__init__.py | 57 ++++++++-------- tests/units/tools/test_pydantic.py | 5 +- 13 files changed, 132 insertions(+), 216 deletions(-) delete mode 100644 anta/reporter/models.py delete mode 100644 docs/api/report_manager_models.md diff --git a/anta/__init__.py b/anta/__init__.py index 85d311cf9..a74f8f9f3 100644 --- a/anta/__init__.py +++ b/anta/__init__.py @@ -32,6 +32,7 @@ class RICH_COLOR_PALETTE: SUCCESS = "green4" SKIPPED = "bold orange4" HEADER = "cyan" + UNSET = "grey74" # Dictionary to use in a Rich.Theme: custom_theme = Theme(RICH_COLOR_THEME) @@ -40,4 +41,5 @@ class RICH_COLOR_PALETTE: "skipped": RICH_COLOR_PALETTE.SKIPPED, "failure": RICH_COLOR_PALETTE.FAILURE, "error": RICH_COLOR_PALETTE.ERROR, + "unset": RICH_COLOR_PALETTE.UNSET, } diff --git a/anta/cli/nrfu/utils.py b/anta/cli/nrfu/utils.py index 49b253697..7275530d0 100644 --- a/anta/cli/nrfu/utils.py +++ b/anta/cli/nrfu/utils.py @@ -1,7 +1,6 @@ # Copyright (c) 2023 Arista Networks, Inc. # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. - """ Utils functions to use with anta.cli.check.commands module. """ @@ -56,27 +55,27 @@ def print_json(results: ResultManager, output: pathlib.Path | None = None) -> No """Print result in a json format""" console.print() console.print(Panel("JSON results of all tests", style="cyan")) - rich.print_json(results.get_results(output_format="json")) + rich.print_json(results.get_json_results()) if output is not None: with open(output, "w", encoding="utf-8") as fout: - fout.write(results.get_results(output_format="json")) + fout.write(results.get_json_results()) def print_list(results: ResultManager, output: pathlib.Path | None = None) -> None: """Print result in a list""" console.print() console.print(Panel.fit("List results of all tests", style="cyan")) - pprint(results.get_results(output_format="list")) + pprint(results.get_results()) if output is not None: with open(output, "w", encoding="utf-8") as fout: - fout.write(str(results.get_results(output_format="list"))) + fout.write(str(results.get_results())) def print_text(results: ResultManager, search: str | None = None, skip_error: bool = False) -> None: """Print results as simple text""" console.print() regexp = re.compile(search or ".*") - for line in results.get_results(output_format="list"): + for line in results.get_results(): if any(regexp.match(entry) for entry in [line.name, line.test]) and (not skip_error or line.result != "error"): message = f" ({str(line.messages[0])})" if len(line.messages) > 0 else "" console.print(f"{line.name} :: {line.test} :: [{line.result}]{line.result.upper()}[/{line.result}]{message}", highlight=False) @@ -86,7 +85,7 @@ def print_jinja(results: ResultManager, template: pathlib.Path, output: pathlib. """Print result based on template.""" console.print() reporter = ReportJinja(template_path=template) - json_data = json.loads(results.get_results(output_format="json")) + json_data = json.loads(results.get_json_results()) report = reporter.render(json_data) console.print(report) if output is not None: diff --git a/anta/reporter/__init__.py b/anta/reporter/__init__.py index 9fc56fc4f..e01b5190c 100644 --- a/anta/reporter/__init__.py +++ b/anta/reporter/__init__.py @@ -14,12 +14,12 @@ from jinja2 import Template from rich.table import Table +from rich.text import Text -from anta import RICH_COLOR_PALETTE +from anta import RICH_COLOR_PALETTE, RICH_COLOR_THEME +from anta.custom_types import TestStatus from anta.result_manager import ResultManager -from .models import ColorManager - logger = logging.getLogger(__name__) @@ -30,11 +30,7 @@ def __init__(self) -> None: """ __init__ Class constructor """ - self.colors = [] - self.colors.append(ColorManager(level="success", color=RICH_COLOR_PALETTE.SUCCESS)) - self.colors.append(ColorManager(level="failure", color=RICH_COLOR_PALETTE.FAILURE)) - self.colors.append(ColorManager(level="error", color=RICH_COLOR_PALETTE.ERROR)) - self.colors.append(ColorManager(level="skipped", color=RICH_COLOR_PALETTE.SKIPPED)) + self.color_manager = ColorManager() def _split_list_to_txt_list(self, usr_list: list[str], delimiter: Optional[str] = None) -> str: """ @@ -71,24 +67,18 @@ def _build_headers(self, headers: list[str], table: Table) -> Table: table.add_column(header, justify="left") return table - def _color_result(self, status: str, output_type: str = "Text") -> Any: + def _color_result(self, status: TestStatus) -> str: """ - Helper to implement color based on test status. - - It gives output for either standard str or Text() colorized with Style() + Return a colored string based on the status value. Args: - status (str): status value to colorized - output_type (str, optional): Which format to output code. Defaults to 'Text'. + status (TestStatus): status value to color Returns: - Any: Can be either str or Text with Style + str: the colored string """ - # TODO refactor this code as it looks quite surprising - if len([result for result in self.colors if str(result.level).upper() == status.upper()]) == 1: - code: ColorManager = [result for result in self.colors if str(result.level).upper() == status.upper()][0] - return code.style_rich() if output_type == "Text" else code.string() - return None + color = RICH_COLOR_THEME.get(status, "") + return f"[{color}]{status}" if color != "" else str(status) def report_all( self, @@ -115,10 +105,10 @@ def report_all( headers = ["Device", "Test Name", "Test Status", "Message(s)", "Test description", "Test category"] table = self._build_headers(headers=headers, table=table) - for result in result_manager.get_results(output_format="list"): + for result in result_manager.get_results(): # pylint: disable=R0916 if (host is None and testcase is None) or (host is not None and str(result.name) == host) or (testcase is not None and testcase == str(result.test)): - state = self._color_result(status=str(result.result), output_type="str") + state = self._color_result(result.result) message = self._split_list_to_txt_list(result.messages) if len(result.messages) > 0 else "" categories = ", ".join(result.categories) table.add_row(str(result.name), result.test, state, message, result.description, categories) @@ -238,7 +228,7 @@ def render(self, data: list[dict[str, Any]], trim_blocks: bool = True, lstrip_bl Report is built based on a J2 template provided by user. Data structure sent to template is: - >>> data = ResultManager.get_results(output_format="json") + >>> data = ResultManager.get_json_results() >>> print(data) [ { @@ -263,3 +253,44 @@ def render(self, data: list[dict[str, Any]], trim_blocks: bool = True, lstrip_bl template = Template(file_.read(), trim_blocks=trim_blocks, lstrip_blocks=lstrip_blocks) return template.render({"data": data}) + + +class ColorManager: + """Color management for status report.""" + + def get_color(self, level: TestStatus) -> str: + """Return the color attributed to the status in RICH_COLOR_THEME. + + Args: + level (TestStatus): The status to colorized + + Returns: + str: the colors attributed to this or empty string + + """ + return RICH_COLOR_THEME.get(level, "") + + def style_rich(self, level: TestStatus) -> Text: + """ + Build a rich Text syntax with color + + Args: + level (TestStatus): The status to colorized + + Returns: + Text: object with level string and its associated color. + """ + return Text(level, style=self.get_color(level)) + + def string(self, level: TestStatus) -> str: + """ + Build an str with color code + + Args: + level (TestStatus): The status to colorized + + Returns: + str: String with level and its associated color + """ + color = self.get_color(level) + return f"[{color}]{level}" if color != "" else str(level) diff --git a/anta/reporter/models.py b/anta/reporter/models.py deleted file mode 100644 index ad1abb52c..000000000 --- a/anta/reporter/models.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2023 Arista Networks, Inc. -# Use of this source code is governed by the Apache License 2.0 -# that can be found in the LICENSE file. -"""Models related to anta.result_manager module.""" - -from pydantic import BaseModel -from rich.text import Text - -from anta.custom_types import TestStatus - - -class ColorManager(BaseModel): - """Color management for status report. - - Attributes: - level (str): Test result value. - color (str): Associated color. - """ - - level: TestStatus - color: str - - def style_rich(self) -> Text: - """ - Build a rich Text syntax with color - - Returns: - Text: object with level string and its associated color. - """ - return Text(self.level, style=self.color) - - def string(self) -> str: - """ - Build an str with color code - - Returns: - str: String with level and its associated color - """ - return f"[{self.color}]{self.level}" diff --git a/anta/result_manager/__init__.py b/anta/result_manager/__init__.py index 191989877..bef7136d1 100644 --- a/anta/result_manager/__init__.py +++ b/anta/result_manager/__init__.py @@ -8,12 +8,11 @@ import json import logging -from typing import Any from pydantic import TypeAdapter from anta.custom_types import TestStatus -from anta.result_manager.models import ListResult, TestResult +from anta.result_manager.models import TestResult from anta.tools.pydantic import pydantic_to_dict logger = logging.getLogger(__name__) @@ -89,7 +88,7 @@ def __init__(self) -> None: If the status of the added test is error, the status is untouched and the error_status is set to True. """ - self._result_entries = ListResult() + self._result_entries: list[TestResult] = [] # Initialize status self.status: TestStatus = "unset" self.error_status = False @@ -140,33 +139,25 @@ def get_status(self, ignore_error: bool = False) -> str: """ return "error" if self.error_status and not ignore_error else self.status - def get_results(self, output_format: str = "native") -> Any: + def get_results(self) -> list[TestResult]: """ Expose list of all test results in different format - Support multiple format: - - native: ListResults format - - list: a list of TestResult - - json: a native JSON format - - Args: - output_format (str, optional): format selector. Can be either native/list/json. Defaults to 'native'. - Returns: any: List of results. """ - if output_format == "list": - return list(self._result_entries) + return self._result_entries - if output_format == "json": - return json.dumps(pydantic_to_dict(self._result_entries), indent=4) + def get_json_results(self) -> str: + """ + Expose list of all test results in JSON - if output_format == "native": - # Default return for native format. - return self._result_entries - raise ValueError(f"{output_format} is not a valid value ['list', 'json', 'native']") + Returns: + str: JSON dumps of the list of results + """ + return json.dumps(pydantic_to_dict(self._result_entries), indent=4) - def get_result_by_test(self, test_name: str, output_format: str = "native") -> Any: + def get_result_by_test(self, test_name: str) -> list[TestResult]: """ Get list of test result for a given test. @@ -177,16 +168,9 @@ def get_result_by_test(self, test_name: str, output_format: str = "native") -> A Returns: list[TestResult]: List of results related to the test. """ - if output_format == "list": - return [result for result in self._result_entries if str(result.test) == test_name] + return [result for result in self._result_entries if str(result.test) == test_name] - result_manager_filtered = ListResult() - for result in self._result_entries: - if result.test == test_name: - result_manager_filtered.append(result) - return result_manager_filtered - - def get_result_by_host(self, host_ip: str, output_format: str = "native") -> Any: + def get_result_by_host(self, host_ip: str) -> list[TestResult]: """ Get list of test result for a given host. @@ -195,16 +179,9 @@ def get_result_by_host(self, host_ip: str, output_format: str = "native") -> Any output_format (str, optional): format selector. Can be either native/list. Defaults to 'native'. Returns: - Any: List of results related to the host. + list[TestResult]: List of results related to the host. """ - if output_format == "list": - return [result for result in self._result_entries if str(result.name) == host_ip] - - result_manager_filtered = ListResult() - for result in self._result_entries: - if str(result.name) == host_ip: - result_manager_filtered.append(result) - return result_manager_filtered + return [result for result in self._result_entries if str(result.name) == host_ip] def get_testcases(self) -> list[str]: """ diff --git a/anta/result_manager/models.py b/anta/result_manager/models.py index 0a582b22a..86ec65d3c 100644 --- a/anta/result_manager/models.py +++ b/anta/result_manager/models.py @@ -4,12 +4,10 @@ """Models related to anta.result_manager module.""" from __future__ import annotations -from collections.abc import Iterator - # Need to keep List for pydantic in 3.8 from typing import List, Optional -from pydantic import BaseModel, ConfigDict, RootModel +from pydantic import BaseModel, ConfigDict from anta.custom_types import TestStatus @@ -95,36 +93,3 @@ def __str__(self) -> str: Returns a human readable string of this TestResult """ return f"Test {self.test} on device {self.name} has result {self.result}" - - -class ListResult(RootModel[List[TestResult]]): - """ - list result for all tests on all devices. - - Attributes: - __root__ (list[TestResult]): A list of TestResult objects. - """ - - root: List[TestResult] = [] - - def extend(self, values: list[TestResult]) -> None: - """Add support for extend method.""" - self.root.extend(values) - - def append(self, value: TestResult) -> None: - """Add support for append method.""" - self.root.append(value) - - def __iter__(self) -> Iterator[TestResult]: # type: ignore - """Use custom iter method.""" - # TODO - mypy is not happy because we overwrite BaseModel.__iter__ - # return type and are breaking Liskov Substitution Principle. - return iter(self.root) - - def __getitem__(self, item: int) -> TestResult: - """Use custom getitem method.""" - return self.root[item] - - def __len__(self) -> int: - """Support for length of __root__""" - return len(self.root) diff --git a/anta/tools/pydantic.py b/anta/tools/pydantic.py index 30e0aa038..9e07ed7f3 100644 --- a/anta/tools/pydantic.py +++ b/anta/tools/pydantic.py @@ -4,19 +4,18 @@ """ Toolkit for ANTA to play with Pydantic. """ - from __future__ import annotations import logging from typing import TYPE_CHECKING, Any, Sequence if TYPE_CHECKING: - from anta.result_manager.models import ListResult + from anta.result_manager.models import TestResult logger = logging.getLogger(__name__) -def pydantic_to_dict(pydantic_list: ListResult) -> list[dict[str, Sequence[Any]]]: +def pydantic_to_dict(pydantic_list: list[TestResult]) -> list[dict[str, Sequence[Any]]]: """ Convert Pydantic object into a list of dict diff --git a/docs/api/report_manager_models.md b/docs/api/report_manager_models.md deleted file mode 100644 index 704faab7f..000000000 --- a/docs/api/report_manager_models.md +++ /dev/null @@ -1,7 +0,0 @@ - - -### ::: anta.reporter.models.ColorManager diff --git a/docs/api/result_manager_models.md b/docs/api/result_manager_models.md index 26c9dc236..0718b8ca6 100644 --- a/docs/api/result_manager_models.md +++ b/docs/api/result_manager_models.md @@ -13,7 +13,3 @@ ### ::: anta.result_manager.models.TestResult options: filters: ["!^_[^_]", "!__str__"] - -### ::: anta.result_manager.models.ListResult - options: - filters: ["!^_[^_]", "!^__(len|getitem|iter)__",] diff --git a/tests/lib/fixture.py b/tests/lib/fixture.py index 6ac7280f0..ef15d0f41 100644 --- a/tests/lib/fixture.py +++ b/tests/lib/fixture.py @@ -2,6 +2,7 @@ # Use of this source code is governed by the Apache License 2.0 # that can be found in the LICENSE file. """Fixture for Anta Testing""" +from __future__ import annotations from typing import Callable from unittest.mock import MagicMock, create_autospec @@ -13,7 +14,7 @@ from anta.device import AntaDevice from anta.inventory import AntaInventory from anta.result_manager import ResultManager -from anta.result_manager.models import ListResult, TestResult +from anta.result_manager.models import TestResult from tests.lib.utils import default_anta_env @@ -41,6 +42,7 @@ def test_result_factory(mocked_device: MagicMock) -> Callable[[int], TestResult] """ Return a anta.result_manager.models.TestResult object """ + # pylint: disable=redefined-outer-name def _create(index: int = 0) -> TestResult: @@ -58,17 +60,18 @@ def _create(index: int = 0) -> TestResult: @pytest.fixture -def list_result_factory(test_result_factory: Callable[[int], TestResult]) -> Callable[[int], ListResult]: +def list_result_factory(test_result_factory: Callable[[int], TestResult]) -> Callable[[int], list[TestResult]]: """ - Return a ListResult with 'size' TestResult instanciated using the test_result_factory fixture + Return a list[TestResult] with 'size' TestResult instanciated using the test_result_factory fixture """ + # pylint: disable=redefined-outer-name - def _factory(size: int = 0) -> ListResult: + def _factory(size: int = 0) -> list[TestResult]: """ - Factory for ListResult entry of size entries + Factory for list[TestResult] entry of size entries """ - result = ListResult() + result: list[TestResult] = [] for i in range(size): result.append(test_result_factory(i)) return result @@ -77,18 +80,19 @@ def _factory(size: int = 0) -> ListResult: @pytest.fixture -def result_manager_factory(list_result_factory: Callable[[int], ListResult]) -> Callable[[int], ResultManager]: +def result_manager_factory(list_result_factory: Callable[[int], list[TestResult]]) -> Callable[[int], ResultManager]: """ Return a ResultManager factory that takes as input a number of tests """ + # pylint: disable=redefined-outer-name def _factory(number: int = 0) -> ResultManager: """ - Factory for ListResult entry of size entries + Factory for list[TestResult] entry of size entries """ result_manager = ResultManager() - result_manager.add_test_results(list_result_factory(number).root) + result_manager.add_test_results(list_result_factory(number)) return result_manager return _factory diff --git a/tests/units/reporter/test__init__.py b/tests/units/reporter/test__init__.py index 0b8f7fa2b..89b1ba676 100644 --- a/tests/units/reporter/test__init__.py +++ b/tests/units/reporter/test__init__.py @@ -4,14 +4,13 @@ """ Test anta.report.__init__.py """ - from __future__ import annotations import pytest from rich.table import Table -from rich.text import Text from anta import RICH_COLOR_PALETTE +from anta.custom_types import TestStatus from anta.reporter import ReportTable @@ -63,24 +62,20 @@ def test__build_headers(self, headers: list[str]) -> None: assert table.columns[table_column_before].style == RICH_COLOR_PALETTE.HEADER @pytest.mark.parametrize( - "status, output_type, expected_status", + "status, expected_status", [ - pytest.param("unknown", None, None, id="unknown status"), - pytest.param("unset", None, None, id="unset status"), - pytest.param("skipped", None, "[bold orange4]skipped", id="skipped status"), - pytest.param("failure", None, "[bold red]failure", id="failure status"), - pytest.param("error", None, "[indian_red]error", id="error status"), - pytest.param("success", None, "[green4]success", id="success status"), - pytest.param("success", "Text", "to_be_replaced", id="Text"), - pytest.param("success", "DUMMY", "[green4]success", id="DUMMY"), + pytest.param("unknown", "unknown", id="unknown status"), + pytest.param("unset", "[grey74]unset", id="unset status"), + pytest.param("skipped", "[bold orange4]skipped", id="skipped status"), + pytest.param("failure", "[bold red]failure", id="failure status"), + pytest.param("error", "[indian_red]error", id="error status"), + pytest.param("success", "[green4]success", id="success status"), ], ) - def test__color_result(self, status: str, output_type: str, expected_status: str | Text) -> None: + def test__color_result(self, status: TestStatus, expected_status: str) -> None: """ test _build_headers """ # pylint: disable=protected-access report = ReportTable() - if output_type == "Text": - expected_status = report.colors[0].style_rich() - assert report._color_result(status, output_type) == expected_status + assert report._color_result(status) == expected_status diff --git a/tests/units/result_manager/test__init__.py b/tests/units/result_manager/test__init__.py index addda8389..7d7510e3f 100644 --- a/tests/units/result_manager/test__init__.py +++ b/tests/units/result_manager/test__init__.py @@ -4,21 +4,17 @@ """ Test anta.result_manager.__init__.py """ - from __future__ import annotations import json from contextlib import nullcontext -from typing import TYPE_CHECKING, Any, Callable +from typing import Any, Callable import pytest from anta.custom_types import TestStatus from anta.result_manager import ResultManager -from anta.result_manager.models import ListResult - -if TYPE_CHECKING: - from anta.result_manager.models import TestResult +from anta.result_manager.models import TestResult class Test_ResultManager: @@ -28,7 +24,7 @@ class Test_ResultManager: # not testing __init__ as nothing is going on there - def test__len__(self, list_result_factory: Callable[[int], ListResult]) -> None: + def test__len__(self, list_result_factory: Callable[[int], list[TestResult]]) -> None: """ test __len__ """ @@ -116,7 +112,7 @@ def test_add_test_result(self, test_result_factory: Callable[[int], TestResult]) assert result_manager.error_status is True assert len(result_manager) == 4 - def test_add_test_results(self, list_result_factory: Callable[[int], ListResult]) -> None: + def test_add_test_results(self, list_result_factory: Callable[[int], list[TestResult]]) -> None: """ Test ResultManager.add_test_results """ @@ -129,7 +125,7 @@ def test_add_test_results(self, list_result_factory: Callable[[int], ListResult] success_list = list_result_factory(3) for test in success_list: test.result = "success" - result_manager.add_test_results(success_list.root) + result_manager.add_test_results(success_list) assert result_manager.status == "success" assert result_manager.error_status is False assert len(result_manager) == 3 @@ -138,7 +134,7 @@ def test_add_test_results(self, list_result_factory: Callable[[int], ListResult] error_failure_list = list_result_factory(2) error_failure_list[0].result = "error" error_failure_list[1].result = "failure" - result_manager.add_test_results(error_failure_list.root) + result_manager.add_test_results(error_failure_list) assert result_manager.status == "failure" assert result_manager.error_status is True assert len(result_manager) == 5 @@ -161,16 +157,7 @@ def test_get_status(self, status: TestStatus, error_status: bool, ignore_error: assert result_manager.get_status(ignore_error=ignore_error) == expected_status - @pytest.mark.parametrize( - "_format, expected_raise", - [ - ("native", nullcontext()), - ("json", nullcontext()), - ("native", nullcontext()), - ("dummy", pytest.raises(ValueError)), - ], - ) - def test_get_results(self, list_result_factory: Callable[[int], ListResult], _format: str, expected_raise: Any) -> None: + def test_get_results(self, list_result_factory: Callable[[int], list[TestResult]]) -> None: """ test ResultManager.get_results """ @@ -179,18 +166,26 @@ def test_get_results(self, list_result_factory: Callable[[int], ListResult], _fo success_list = list_result_factory(3) for test in success_list: test.result = "success" - result_manager.add_test_results(success_list.root) + result_manager.add_test_results(success_list) - with expected_raise: - res = result_manager.get_results(output_format=_format) - if _format == "json": - assert isinstance(res, str) - # verifies it can be loaded as json - json.loads(res) - elif _format == "list": - assert isinstance(res, list) - elif _format == "native": - assert isinstance(res, ListResult) + res = result_manager.get_results() + assert isinstance(res, list) + + def test_get_json_results(self, list_result_factory: Callable[[int], list[TestResult]]) -> None: + """ + test ResultManager.get_json_results + """ + result_manager = ResultManager() + + success_list = list_result_factory(3) + for test in success_list: + test.result = "success" + result_manager.add_test_results(success_list) + + res = result_manager.get_json_results() + assert isinstance(res, str) + # verifies it can be loaded as json + json.loads(res) # TODO # get_result_by_test diff --git a/tests/units/tools/test_pydantic.py b/tests/units/tools/test_pydantic.py index b5d75b574..d9601ec2d 100644 --- a/tests/units/tools/test_pydantic.py +++ b/tests/units/tools/test_pydantic.py @@ -4,7 +4,6 @@ """ Tests for anta.tools.pydantic """ - from __future__ import annotations from typing import TYPE_CHECKING, Any, Callable @@ -14,7 +13,7 @@ from anta.tools.pydantic import pydantic_to_dict if TYPE_CHECKING: - from anta.result_manager.models import ListResult + from anta.result_manager.models import TestResult EXPECTED_ONE_ENTRY = [ { @@ -71,7 +70,7 @@ ], ) def test_pydantic_to_dict( - list_result_factory: Callable[[int], ListResult], + list_result_factory: Callable[[int], list[TestResult]], number_of_entries: int, expected: dict[str, Any], ) -> None: