Skip to content

netops.report — Reporting & Scheduling

HTML/PDF report generation, health dashboard, email delivery, and report scheduling.


netops.report.generator

Generate HTML (and optionally PDF) health reports from check results.

netops.report.generator is a Python API; it does not define a command-line interface. Build a report from structured check results, then render it:

from netops.report import ReportGenerator

report = ReportGenerator()
data = report.build_report(title="Network Health", sections=[])
report.generate_html(data, output_path="report.html")

generator

Report generator — produce HTML and PDF network health reports from check results.

Combines results from :mod:netops.check.health, :mod:netops.check.bgp, :mod:netops.check.vlan (and any other check module) into formatted reports.

Requires the optional report extra::

pip install netops-toolkit[report]          # HTML only
pip install netops-toolkit[report-pdf]      # HTML + PDF

Usage::

from netops.check.health import build_health_report
from netops.check.bgp import build_bgp_report
from netops.report.generator import ReportGenerator

gen = ReportGenerator()
report_data = gen.build_report(
    title="Weekly Network Health",
    sections=[
        {"name": "Device Health", "type": "health",
         "data": build_health_report(health_results)},
        {"name": "BGP Health",    "type": "bgp",
         "data": build_bgp_report(bgp_results)},
    ],
)

html = gen.generate_html(report_data, output_path="report.html")
pdf  = gen.generate_pdf(report_data,  output_path="report.pdf")   # needs [report-pdf]
Classes
ReportSection

Bases: TypedDict

A single section within a report, grouping data by type.

ReportData

Bases: TypedDict

Assembled report data structure produced by :meth:ReportGenerator.build_report.

ReportDataWithHtml

Bases: ReportData

Extension of :class:ReportData that includes the rendered HTML string.

Produced by the :func:generate_report convenience function.

ReportGenerator
ReportGenerator(template_path: str | None = None, output_dir: str | None = None)

Generate HTML (and optionally PDF) network health reports.

Parameters:

Name Type Description Default
template_path str | None

Path to a custom Jinja2 HTML template. When None the bundled default.html.j2 template is used.

None
output_dir str | None

Default directory for :meth:generate_html / :meth:generate_pdf when no explicit output_path is given. Defaults to the current working directory.

None

Initialise the generator with an optional template path and output directory.

Attributes
custom_template_path property
custom_template_path: str | None

Return the custom template path, or None if using the built-in.

Methods:
build_report
build_report(title: str = 'Network Health Report', sections: list[ReportSection] | None = None, period: str | None = None) -> ReportData

Assemble a report data structure ready for rendering.

Parameters:

Name Type Description Default
title str

Report title shown in the HTML header.

'Network Health Report'
sections list[ReportSection] | None

List of section dicts, each with keys:

  • name – human-readable section heading
  • type – one of "health", "bgp", "vlan" (or any string; unknown types fall back to a raw JSON view)
  • data – the dict returned by the corresponding build_*_report() function
None
period str | None

Optional description of the reporting period, e.g. "2024-03-23 to 2024-03-24".

None

Returns:

Type Description
ReportData

Dict with keys title, generated_at, period, sections, and overall_alert.

generate_html
generate_html(report_data: ReportData, output_path: str | None = None) -> str

Render report_data as an HTML string.

Parameters:

Name Type Description Default
report_data ReportData

Dict produced by :meth:build_report.

required
output_path str | None

If given, the HTML is written to this file path in addition to being returned. Relative paths are resolved against output_dir.

None

Returns:

Type Description
str

Rendered HTML string.

Raises:

Type Description
ImportError

When jinja2 is not installed (pip install netops-toolkit[report]).

generate_pdf
generate_pdf(report_data: ReportData, output_path: str | None = None) -> bytes

Render report_data as a PDF document.

Internally renders HTML via :meth:generate_html then converts it to PDF using weasyprint.

Parameters:

Name Type Description Default
report_data ReportData

Dict produced by :meth:build_report.

required
output_path str | None

If given, the PDF bytes are written to this file path in addition to being returned.

None

Returns:

Type Description
bytes

PDF document bytes.

Raises:

Type Description
ImportError

When weasyprint is not installed (pip install netops-toolkit[report-pdf]).

Functions:
default_output_filename
default_output_filename(prefix: str = 'netops-report', fmt: str = 'html') -> str

Return a timestamped filename like netops-report-20240324-120000.html.

generate_report
generate_report(sections: list[ReportSection], title: str = 'Network Health Report', period: str | None = None, output_dir: str | None = None, template_path: str | None = None, html_output: str | None = 'auto', pdf_output: str | None = None) -> ReportDataWithHtml

High-level convenience wrapper: build report, render HTML (and PDF).

Parameters:

Name Type Description Default
sections list[ReportSection]

List of section dicts (name, type, data).

required
title str

Report title.

'Network Health Report'
period str | None

Optional reporting period description.

None
output_dir str | None

Directory for output files. Defaults to the current directory.

None
template_path str | None

Custom Jinja2 template path (optional).

None
html_output str | None

Path for the HTML output file. Use "auto" to generate a timestamped filename in output_dir. Use None to skip writing.

'auto'
pdf_output str | None

Path for the PDF output file. Use "auto" for a timestamped name. Requires weasyprint (pip install netops-toolkit[report-pdf]).

None

Returns:

Type Description
ReportDataWithHtml

Assembled report data dict with an additional html key containing the rendered HTML string.


netops.report.health_dashboard

Aggregate device health results into a summary dashboard view.

Supports table (terminal), JSON, and HTML output formats.

CLI usage:

python -m netops.report.health_dashboard --inventory inventory.yaml
python -m netops.report.health_dashboard --inventory inventory.yaml --format json
python -m netops.report.health_dashboard --inventory inventory.yaml --format html \
    --output dashboard.html

health_dashboard

Unified multi-vendor health dashboard.

Aggregates health check results from all supported vendor checkers (Cisco IOS/IOS-XE, Arista EOS, Juniper JunOS, Nokia SROS, Brocade, Palo Alto PAN-OS) into a single normalised view and renders it as a terminal table, JSON document, or self-contained HTML page.

Each vendor check result is normalised to a common row schema::

{
    "device":    <str>,          # hostname / IP
    "vendor":    <str>,          # device_type string
    "site":      <str | None>,   # optional site tag
    "category":  <str>,          # "cpu", "memory", "interfaces", ...
    "status":    <str>,          # "ok", "warn", or "crit"
    "detail":    <str>,          # human-readable one-liner
    "timestamp": <str>,          # ISO-8601 UTC
}

Usage::

python -m netops.report.health_dashboard \\
    --inventory inv.yaml --group core \\
    --format table

python -m netops.report.health_dashboard \\
    --inventory inv.yaml --vendor arista_eos \\
    --format html --output dashboard.html

Programmatic::

from netops.report.health_dashboard import aggregate_dashboard, format_table

results = [run_health_check(p) for p in device_params]
dashboard = aggregate_dashboard(results, vendor_tag="cisco_ios")
print(format_table(dashboard))
Functions:
normalize_device_result
normalize_device_result(result: dict, vendor: str | None = None, site: str | None = None) -> list[dict]

Convert a per-device health-check result to a list of normalised rows.

Each row represents one check category and follows the common schema::

{
    "device":    <str>,
    "vendor":    <str | None>,
    "site":      <str | None>,
    "category":  <str>,
    "status":    "ok" | "warn" | "crit",
    "detail":    <str>,
    "timestamp": <str>,
}

Parameters:

Name Type Description Default
result dict

A dict returned by any run_*_health_check() function.

required
vendor str | None

Vendor/platform tag to attach to each row (e.g. "cisco_ios"). If None the raw key from result is used when available.

None
site str | None

Optional site/location label.

None

Returns:

Type Description
list

List of row dicts. Unreachable devices produce a single status="crit" row with category "reachability".

aggregate_dashboard
aggregate_dashboard(device_results: list[dict], vendor_tag: str | None = None, site_tag: str | None = None, filter_vendor: str | None = None, filter_site: str | None = None, filter_severity: str | None = None) -> dict

Aggregate health check results into a unified dashboard dict.

Parameters:

Name Type Description Default
device_results list[dict]

List of per-device result dicts from any run_*_health_check().

required
vendor_tag str | None

Vendor label to attach to every row when the result dicts do not already carry one (e.g. when all devices share a vendor).

None
site_tag str | None

Site label to attach to every row.

None
filter_vendor str | None

When set, only include rows whose vendor contains this string (case-insensitive).

None
filter_site str | None

When set, only include rows matching this site label exactly.

None
filter_severity str | None

When set to "warn" or "crit", exclude "ok" rows. When set to "crit", also exclude "warn" rows.

None

Returns:

Type Description
dict

Dashboard dict with keys:

  • generated_at – ISO-8601 UTC generation timestamp
  • filters – dict of active filter values
  • entries – list of normalised row dicts
  • summary – aggregated statistics
  • overall_status – worst status across all entries
format_table
format_table(dashboard: dict, color: bool = True) -> str

Render dashboard as a fixed-width terminal table string.

Parameters:

Name Type Description Default
dashboard dict

Dict returned by :func:aggregate_dashboard.

required
color bool

When True (default), status values are prefixed with emoji icons.

True

Returns:

Type Description
str

Formatted string (no trailing newline).

render_html
render_html(dashboard: dict, output_path: str | None = None) -> str

Render dashboard as a self-contained HTML string.

Parameters:

Name Type Description Default
dashboard dict

Dict returned by :func:aggregate_dashboard.

required
output_path str | None

When given, the HTML is also written to this path.

None

Returns:

Type Description
str

Rendered HTML string.

Raises:

Type Description
ImportError

When jinja2 is not installed.

main
main() -> None

CLI entry point: python -m netops.report.health_dashboard.


netops.report.mailer

Send reports via email (SMTP with optional TLS/SSL).

mailer

Email delivery for network health reports.

Sends HTML (and optional PDF attachment) reports via SMTP using Python's built-in :mod:smtplib and :mod:email modules — no extra dependencies required.

Usage::

from netops.report.mailer import ReportMailer

mailer = ReportMailer(
    host="smtp.example.com",
    port=587,
    username="netops@example.com",
    password="secret",
    use_tls=True,
)
mailer.send(
    recipients=["ops-team@example.com"],
    subject="Daily Network Health Report",
    html_body=html_str,
    pdf_attachment=pdf_bytes,   # optional
)
Classes
ReportMailer
ReportMailer(host: str, port: int = 587, username: str | None = None, password: str | None = None, use_tls: bool = True, use_ssl: bool = False, from_addr: str | None = None, timeout: int = 30)

Send HTML reports via SMTP.

Parameters:

Name Type Description Default
host str

SMTP server hostname or IP address.

required
port int

SMTP port (default: 587 for STARTTLS).

587
username str | None

SMTP authentication username. When None no authentication is attempted.

None
password str | None

SMTP authentication password.

None
use_tls bool

When True (default) upgrade the connection with STARTTLS.

True
use_ssl bool

When True open a direct SSL/TLS connection (typically port 465). Mutually exclusive with use_tlsuse_ssl takes precedence.

False
from_addr str | None

Envelope From address. Defaults to username when not given.

None
timeout int

Socket timeout in seconds (default: 30).

30

Initialise the mailer with SMTP server connection settings.

Methods:
send
send(recipients: list[str], subject: str, html_body: str, pdf_attachment: bytes | None = None, pdf_filename: str = 'report.pdf', plain_text: str | None = None) -> None

Send a report email to recipients.

Parameters:

Name Type Description Default
recipients list[str]

List of recipient email addresses.

required
subject str

Email subject line.

required
html_body str

HTML content for the email body.

required
pdf_attachment bytes | None

Optional PDF bytes to attach to the message.

None
pdf_filename str

Filename for the PDF attachment (default: "report.pdf").

'report.pdf'
plain_text str | None

Optional plain-text alternative body. When omitted a minimal plain-text version is generated automatically.

None

netops.report.scheduler

Schedule recurring report generation and delivery.

Supports daily and weekly schedules with configurable delivery windows.

scheduler

Scheduled network health report generation.

Supports daily and weekly schedules using Python's built-in :mod:threading module — no external scheduler library required.

Usage::

from netops.report.scheduler import ReportScheduler
from netops.report.generator import ReportGenerator
from netops.report.mailer import ReportMailer

gen  = ReportGenerator(output_dir="/var/reports")
mail = ReportMailer(host="smtp.example.com", username="netops@example.com",
                    password="secret")

def collect() -> list[ReportSection]:
    # Build section list from live checks
    return [{"name": "Device Health", "type": "health", "data": {}}]

scheduler = ReportScheduler(generator=gen, mailer=mail)
scheduler.schedule_daily(
    collect_fn=collect,
    time_of_day="06:00",
    recipients=["ops@example.com"],
    subject="Daily Network Health Report",
    pdf=True,
)
scheduler.start()   # blocks; call scheduler.stop() from another thread
Classes
ScheduledReport
ScheduledReport(collect_fn: Callable[[], list[ReportSection]], frequency: str, time_of_day: str, day_of_week: str | None, title: str, output_dir: str | None, recipients: list[str] | None, subject: str | None, pdf: bool)

Descriptor for a single scheduled report job.

Initialise a scheduled report descriptor with timing and delivery settings.

Methods:
next_run
next_run(now: datetime | None = None) -> datetime

Return the next UTC run datetime for this schedule.

ReportScheduler
ReportScheduler(generator: ReportGenerator | None = None, mailer: object | None = None)

Schedule and run periodic network health reports.

Parameters:

Name Type Description Default
generator ReportGenerator | None

:class:~netops.report.generator.ReportGenerator instance used to render HTML (and optionally PDF) output. When None a default generator is created.

None
mailer object | None

Optional :class:~netops.report.mailer.ReportMailer instance for email delivery. When None reports are only written to disk.

None

Initialise the scheduler with optional generator and mailer instances.

Methods:
schedule_daily
schedule_daily(collect_fn: Callable[[], list[ReportSection]], time_of_day: str = '00:00', title: str = 'Daily Network Health Report', output_dir: str | None = None, recipients: list[str] | None = None, subject: str | None = None, pdf: bool = False) -> None

Register a daily report.

Parameters:

Name Type Description Default
collect_fn Callable[[], list[ReportSection]]

Zero-argument callable that returns a list of section dicts ({"name": ..., "type": ..., "data": ...}). Called at report time to gather fresh check results.

required
time_of_day str

UTC time to run the report in HH:MM format (default: "00:00").

'00:00'
title str

Report title.

'Daily Network Health Report'
output_dir str | None

Directory where generated files are saved.

None
recipients list[str] | None

Email recipients. Requires mailer to be set on the scheduler.

None
subject str | None

Email subject. Defaults to title.

None
pdf bool

When True also generate and attach a PDF. Requires weasyprint (pip install netops-toolkit[report-pdf]).

False
schedule_weekly
schedule_weekly(collect_fn: Callable[[], list[ReportSection]], day_of_week: str = 'monday', time_of_day: str = '00:00', title: str = 'Weekly Network Health Report', output_dir: str | None = None, recipients: list[str] | None = None, subject: str | None = None, pdf: bool = False) -> None

Register a weekly report.

Parameters:

Name Type Description Default
collect_fn Callable[[], list[ReportSection]]

Zero-argument callable returning section dicts.

required
day_of_week str

Day to run ("monday""sunday", default: "monday").

'monday'
time_of_day str

UTC time in HH:MM format (default: "00:00").

'00:00'
title str

Report title.

'Weekly Network Health Report'
output_dir str | None

Same as :meth:schedule_daily.

None
recipients str | None

Same as :meth:schedule_daily.

None
subject str | None

Same as :meth:schedule_daily.

None
pdf str | None

Same as :meth:schedule_daily.

None
start
start(blocking: bool = True) -> None

Start the scheduler.

Parameters:

Name Type Description Default
blocking bool

When True (default) this call blocks until :meth:stop is called from another thread. When False the scheduler runs in a background daemon thread and this call returns immediately.

True
stop
stop() -> None

Signal the scheduler to stop after the current sleep cycle.

Functions: