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 ¶
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
|
None
|
output_dir
|
str | None
|
Default directory for :meth: |
None
|
Initialise the generator with an optional template path and output directory.
property
¶Return the custom template path, or None if using the built-in.
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:
|
None
|
period
|
str | None
|
Optional description of the reporting period, e.g.
|
None
|
Returns:
| Type | Description |
|---|---|
ReportData
|
Dict with keys |
Render report_data as an HTML string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report_data
|
ReportData
|
Dict produced by :meth: |
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 |
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: |
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 |
Functions:¶
default_output_filename ¶
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 ( |
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'
|
pdf_output
|
str | None
|
Path for the PDF output file. Use |
None
|
Returns:
| Type | Description |
|---|---|
ReportDataWithHtml
|
Assembled report data dict with an additional |
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 |
required |
vendor
|
str | None
|
Vendor/platform tag to attach to each row (e.g. |
None
|
site
|
str | None
|
Optional site/location label. |
None
|
Returns:
| Type | Description |
|---|---|
list
|
List of row dicts. Unreachable devices produce a single
|
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 |
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 |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dashboard dict with keys:
|
format_table ¶
Render dashboard as a fixed-width terminal table string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dashboard
|
dict
|
Dict returned by :func: |
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 dashboard as a self-contained HTML string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dashboard
|
dict
|
Dict returned by :func: |
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 |
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
|
use_ssl
|
bool
|
When |
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.
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'
|
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 ¶
ReportScheduler ¶
Schedule and run periodic network health reports.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
generator
|
ReportGenerator | None
|
:class: |
None
|
mailer
|
object | None
|
Optional :class: |
None
|
Initialise the scheduler with optional generator and mailer instances.
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
( |
required |
time_of_day
|
str
|
UTC time to run the report in |
'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 |
False
|
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'
|
time_of_day
|
str
|
UTC time in |
'00:00'
|
title
|
str
|
Report title. |
'Weekly Network Health Report'
|
output_dir
|
str | None
|
Same as :meth: |
None
|
recipients
|
str | None
|
Same as :meth: |
None
|
subject
|
str | None
|
Same as :meth: |
None
|
pdf
|
str | None
|
Same as :meth: |
None
|
Start the scheduler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
blocking
|
bool
|
When |
True
|