Skip to content

Markdown Reporter

Markdown report generator for ANTA test results.

ANTAReport

ANTAReport(mdfile: TextIO, results: ResultManager)

Bases: MDReportBase

Generate the # ANTA Report section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_section

generate_section() -> None

Generate the # ANTA Report section of the markdown report.

Source code in anta/reporter/md_reporter.py
190
191
192
193
194
def generate_section(self) -> None:
    """Generate the `# ANTA Report` section of the markdown report."""
    self.write_heading(heading_level=1)
    toc = MD_REPORT_TOC
    self.mdfile.write(toc + "\n\n")

MDReportBase

MDReportBase(mdfile: TextIO, results: ResultManager)

Bases: ABC

Base class for all sections subclasses.

Every subclasses must implement the generate_section method that uses the ResultManager object to generate and write content to the provided markdown file.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_heading_name

generate_heading_name() -> str

Generate a formatted heading name based on the class name.

Returns:

Type Description
str

Formatted header name.

Example
  • ANTAReport will become ANTA Report.
  • TestResultsSummary will become Test Results Summary.
Source code in anta/reporter/md_reporter.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def generate_heading_name(self) -> str:
    """Generate a formatted heading name based on the class name.

    Returns
    -------
    str
        Formatted header name.

    Example
    -------
    - `ANTAReport` will become `ANTA Report`.
    - `TestResultsSummary` will become `Test Results Summary`.
    """
    class_name = self.__class__.__name__

    # Split the class name into words, keeping acronyms together
    words = re.findall(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+", class_name)

    # Capitalize each word, but keep acronyms in all caps
    formatted_words = [word if word.isupper() else word.capitalize() for word in words]

    return " ".join(formatted_words)

generate_rows

generate_rows() -> Generator[str, None, None]

Generate the rows of a markdown table for a specific report section.

Subclasses can implement this method to generate the content of the table rows.

Source code in anta/reporter/md_reporter.py
 96
 97
 98
 99
100
101
102
def generate_rows(self) -> Generator[str, None, None]:
    """Generate the rows of a markdown table for a specific report section.

    Subclasses can implement this method to generate the content of the table rows.
    """
    msg = "Subclasses should implement this method"
    raise NotImplementedError(msg)

generate_section abstractmethod

generate_section() -> None

Abstract method to generate a specific section of the markdown report.

Must be implemented by subclasses.

Source code in anta/reporter/md_reporter.py
87
88
89
90
91
92
93
94
@abstractmethod
def generate_section(self) -> None:
    """Abstract method to generate a specific section of the markdown report.

    Must be implemented by subclasses.
    """
    msg = "Must be implemented by subclasses"
    raise NotImplementedError(msg)

safe_markdown

safe_markdown(text: str | None) -> str

Escape markdown characters in the text to prevent markdown rendering issues.

Parameters:

Name Type Description Default
text str | None

The text to escape markdown characters from.

required

Returns:

Type Description
str

The text with escaped markdown characters.

Source code in anta/reporter/md_reporter.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def safe_markdown(self, text: str | None) -> str:
    """Escape markdown characters in the text to prevent markdown rendering issues.

    Parameters
    ----------
    text
        The text to escape markdown characters from.

    Returns
    -------
    str
        The text with escaped markdown characters.
    """
    # Custom field from a TestResult object can be None
    if text is None:
        return ""

    # Replace newlines with spaces to keep content on one line
    text = text.replace("\n", " ")

    # Replace backticks with single quotes
    return text.replace("`", "'")

write_heading

write_heading(heading_level: int) -> None

Write a markdown heading to the markdown file.

The heading name used is the class name.

Parameters:

Name Type Description Default
heading_level int

The level of the heading (1-6).

required
Example

## Test Results Summary

Source code in anta/reporter/md_reporter.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def write_heading(self, heading_level: int) -> None:
    """Write a markdown heading to the markdown file.

    The heading name used is the class name.

    Parameters
    ----------
    heading_level
        The level of the heading (1-6).

    Example
    -------
    `## Test Results Summary`
    """
    # Ensure the heading level is within the valid range of 1 to 6
    heading_level = max(1, min(heading_level, 6))
    heading_name = self.generate_heading_name()
    heading = "#" * heading_level + " " + heading_name
    self.mdfile.write(f"{heading}\n\n")

write_table

write_table(
    table_heading: list[str], *, last_table: bool = False
) -> None

Write a markdown table with a table heading and multiple rows to the markdown file.

Parameters:

Name Type Description Default
table_heading list[str]

List of strings to join for the table heading.

required
last_table bool

Flag to determine if it’s the last table of the markdown file to avoid unnecessary new line. Defaults to False.

False
Source code in anta/reporter/md_reporter.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def write_table(self, table_heading: list[str], *, last_table: bool = False) -> None:
    """Write a markdown table with a table heading and multiple rows to the markdown file.

    Parameters
    ----------
    table_heading
        List of strings to join for the table heading.
    last_table
        Flag to determine if it's the last table of the markdown file to avoid unnecessary new line. Defaults to False.
    """
    self.mdfile.write("\n".join(table_heading) + "\n")
    for row in self.generate_rows():
        self.mdfile.write(row)
    if not last_table:
        self.mdfile.write("\n")

MDReportGenerator

Class responsible for generating a Markdown report based on the provided ResultManager object.

It aggregates different report sections, each represented by a subclass of MDReportBase, and sequentially generates their content into a markdown file.

The generate class method will loop over all the section subclasses and call their generate_section method. The final report will be generated in the same order as the sections list of the method.

generate classmethod

generate(results: ResultManager, md_filename: Path) -> None

Generate and write the various sections of the markdown report.

Parameters:

Name Type Description Default
results ResultManager

The ResultsManager instance containing all test results.

required
md_filename Path

The path to the markdown file to write the report into.

required
Source code in anta/reporter/md_reporter.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@classmethod
def generate(cls, results: ResultManager, md_filename: Path) -> None:
    """Generate and write the various sections of the markdown report.

    Parameters
    ----------
    results
        The ResultsManager instance containing all test results.
    md_filename
        The path to the markdown file to write the report into.
    """
    try:
        with md_filename.open("w", encoding="utf-8") as mdfile:
            sections: list[MDReportBase] = [
                ANTAReport(mdfile, results),
                TestResultsSummary(mdfile, results),
                SummaryTotals(mdfile, results),
                SummaryTotalsDeviceUnderTest(mdfile, results),
                SummaryTotalsPerCategory(mdfile, results),
                TestResults(mdfile, results),
            ]
            for section in sections:
                section.generate_section()
    except OSError as exc:
        message = f"OSError caught while writing the Markdown file '{md_filename.resolve()}'."
        anta_log_exception(exc, message, logger)
        raise

SummaryTotals

SummaryTotals(mdfile: TextIO, results: ResultManager)

Bases: MDReportBase

Generate the ### Summary Totals section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_rows

generate_rows() -> Generator[str, None, None]

Generate the rows of the summary totals table.

Source code in anta/reporter/md_reporter.py
213
214
215
216
217
218
219
220
221
def generate_rows(self) -> Generator[str, None, None]:
    """Generate the rows of the summary totals table."""
    yield (
        f"| {self.results.get_total_results()} "
        f"| {self.results.get_total_results({AntaTestStatus.SUCCESS})} "
        f"| {self.results.get_total_results({AntaTestStatus.SKIPPED})} "
        f"| {self.results.get_total_results({AntaTestStatus.FAILURE})} "
        f"| {self.results.get_total_results({AntaTestStatus.ERROR})} |\n"
    )

generate_section

generate_section() -> None

Generate the ### Summary Totals section of the markdown report.

Source code in anta/reporter/md_reporter.py
223
224
225
226
def generate_section(self) -> None:
    """Generate the `### Summary Totals` section of the markdown report."""
    self.write_heading(heading_level=3)
    self.write_table(table_heading=self.TABLE_HEADING)

SummaryTotalsDeviceUnderTest

SummaryTotalsDeviceUnderTest(
    mdfile: TextIO, results: ResultManager
)

Bases: MDReportBase

Generate the ### Summary Totals Devices Under Tests section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_rows

generate_rows() -> Generator[str, None, None]

Generate the rows of the summary totals device under test table.

Source code in anta/reporter/md_reporter.py
237
238
239
240
241
242
243
244
245
246
def generate_rows(self) -> Generator[str, None, None]:
    """Generate the rows of the summary totals device under test table."""
    for device, stat in self.results.device_stats.items():
        total_tests = stat.tests_success_count + stat.tests_skipped_count + stat.tests_failure_count + stat.tests_error_count
        categories_skipped = ", ".join(sorted(convert_categories(list(stat.categories_skipped))))
        categories_failed = ", ".join(sorted(convert_categories(list(stat.categories_failed))))
        yield (
            f"| {device} | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_failure_count} | {stat.tests_error_count} "
            f"| {categories_skipped or '-'} | {categories_failed or '-'} |\n"
        )

generate_section

generate_section() -> None

Generate the ### Summary Totals Devices Under Tests section of the markdown report.

Source code in anta/reporter/md_reporter.py
248
249
250
251
def generate_section(self) -> None:
    """Generate the `### Summary Totals Devices Under Tests` section of the markdown report."""
    self.write_heading(heading_level=3)
    self.write_table(table_heading=self.TABLE_HEADING)

SummaryTotalsPerCategory

SummaryTotalsPerCategory(
    mdfile: TextIO, results: ResultManager
)

Bases: MDReportBase

Generate the ### Summary Totals Per Category section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_rows

generate_rows() -> Generator[str, None, None]

Generate the rows of the summary totals per category table.

Source code in anta/reporter/md_reporter.py
262
263
264
265
266
267
268
269
def generate_rows(self) -> Generator[str, None, None]:
    """Generate the rows of the summary totals per category table."""
    for category, stat in self.results.sorted_category_stats.items():
        total_tests = stat.tests_success_count + stat.tests_skipped_count + stat.tests_failure_count + stat.tests_error_count
        yield (
            f"| {category} | {total_tests} | {stat.tests_success_count} | {stat.tests_skipped_count} | {stat.tests_failure_count} "
            f"| {stat.tests_error_count} |\n"
        )

generate_section

generate_section() -> None

Generate the ### Summary Totals Per Category section of the markdown report.

Source code in anta/reporter/md_reporter.py
271
272
273
274
def generate_section(self) -> None:
    """Generate the `### Summary Totals Per Category` section of the markdown report."""
    self.write_heading(heading_level=3)
    self.write_table(table_heading=self.TABLE_HEADING)

TestResults

TestResults(mdfile: TextIO, results: ResultManager)

Bases: MDReportBase

Generates the ## Test Results section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_rows

generate_rows() -> Generator[str, None, None]

Generate the rows of the all test results table.

Source code in anta/reporter/md_reporter.py
285
286
287
288
289
290
291
292
293
def generate_rows(self) -> Generator[str, None, None]:
    """Generate the rows of the all test results table."""
    for result in self.results.get_results(sort_by=["name", "test"]):
        messages = self.safe_markdown(", ".join(result.messages))
        categories = ", ".join(convert_categories(result.categories))
        yield (
            f"| {result.name or '-'} | {categories or '-'} | {result.test or '-'} "
            f"| {result.description or '-'} | {self.safe_markdown(result.custom_field) or '-'} | {result.result or '-'} | {messages or '-'} |\n"
        )

generate_section

generate_section() -> None

Generate the ## Test Results section of the markdown report.

Source code in anta/reporter/md_reporter.py
295
296
297
298
def generate_section(self) -> None:
    """Generate the `## Test Results` section of the markdown report."""
    self.write_heading(heading_level=2)
    self.write_table(table_heading=self.TABLE_HEADING, last_table=True)

TestResultsSummary

TestResultsSummary(mdfile: TextIO, results: ResultManager)

Bases: MDReportBase

Generate the ## Test Results Summary section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIO

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required

generate_section

generate_section() -> None

Generate the ## Test Results Summary section of the markdown report.

Source code in anta/reporter/md_reporter.py
200
201
202
def generate_section(self) -> None:
    """Generate the `## Test Results Summary` section of the markdown report."""
    self.write_heading(heading_level=2)