Skip to content

Markdown Reporter

Markdown report generator for ANTA test results.

ANTAReport

ANTAReport(mdfile: TextIOWrapper, results: ResultManager)

Bases: MDReportBase

Generate the # ANTA Report section of the markdown report.

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

generate_section

generate_section() -> None

Generate the # ANTA Report section of the markdown report.

Source code in anta/reporter/md_reporter.py
191
192
193
194
195
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: TextIOWrapper, 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 TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
 97
 98
 99
100
101
102
103
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
88
89
90
91
92
93
94
95
@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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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
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
65
@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: TextIOWrapper, results: ResultManager
)

Bases: MDReportBase

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

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

generate_rows

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

Generate the rows of the summary totals table.

Source code in anta/reporter/md_reporter.py
214
215
216
217
218
219
220
221
222
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
224
225
226
227
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: TextIOWrapper, results: ResultManager
)

Bases: MDReportBase

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

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

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
238
239
240
241
242
243
244
245
246
247
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
249
250
251
252
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: TextIOWrapper, results: ResultManager
)

Bases: MDReportBase

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

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

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
263
264
265
266
267
268
269
270
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
272
273
274
275
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: TextIOWrapper, results: ResultManager)

Bases: MDReportBase

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

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

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
286
287
288
289
290
291
292
293
294
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
296
297
298
299
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: TextIOWrapper, results: ResultManager
)

Bases: MDReportBase

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

Parameters:

Name Type Description Default
mdfile TextIOWrapper

An open file object to write the markdown data into.

required
results ResultManager

The ResultsManager instance containing all test results.

required
Source code in anta/reporter/md_reporter.py
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(self, mdfile: TextIOWrapper, results: ResultManager) -> None:
    """Initialize the MDReportBase with an open markdown file object to write to and a ResultManager instance.

    Parameters
    ----------
    mdfile
        An open file object to write the markdown data into.
    results
        The ResultsManager instance containing all test results.
    """
    self.mdfile = mdfile
    self.results = results

generate_section

generate_section() -> None

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

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