10.2.0
Changes
- Invalid reports now explain why they are invalid. The parser still catches broadly so one malformed report can never crash a batch, but the diagnostics are no longer a bare
Not a valid report.parse_report_file()keeps each format parser's specific error as it tries aggregate XML → SMTP TLS JSON → report email, and when all three reject the input it sniffs the content shape to surface the single relevant reason — e.g.Invalid aggregate report: Missing field: 'org_name',Invalid SMTP TLS report: Missing required field: date-range, orNot a recognized report format (not a DMARC aggregate XML report, an SMTP TLS JSON report, or a DMARC report email). This is the message the CLI already logs (Failed to parse <file> - <reason>), so operators see the cause without a code change on their end. - Original exceptions are now chained. Every parser catch site re-raises with
raise … from <original>, so the underlyingExpatError,json.JSONDecodeError,KeyError, and archive/decompression errors are preserved on__cause__for library callers and tracebacks instead of being flattened into a string. Behavior is otherwise unchanged — the same exception types are still raised. - Unexpected errors cite their origin in debug mode. When a catch-all branch turns an unforeseen exception into a generic
Unexpected error: …/ archive / mail-parse failure, the message now appends(raised at <file>:<line>)pinpointing the deepest traceback frame — but only when theparsedmarclogger is atDEBUGlevel (e.g. the CLI's--debug). Normal-level output is unchanged. - Added per-version
Programming Language :: Python :: 3.10–3.14(and3 :: Only) trove classifiers so the PyPI page advertises the supported Python versions. No change torequires-python(>=3.10).
Bug fixes
- Fixed an
IndexErrorwhen backfillingenvelope_fromfrom SPF results. In_parse_report_record(), when an aggregate report's<identifiers>carried an emptyenvelope_from, the fallback checked the rawauth_results["spf"]list for length but then indexed the filterednew_record["auth_results"]["spf"]list (which only contains results that have adomain). A reporter that sent an SPF auth result with nodomainmade the filtered list empty while the raw list was non-empty, so the[-1]index raisedIndexErrorand the whole record failed to parse. The two near-identicalenvelope_frombackfill branches (one for a missing identifier, one for an empty one) are now a single code path that gates and indexes the same list, matching the already-correct missing-identifier branch.