Issue Type
Environment
EdgarTools Version: 5.53.0
Python Version: 3.14.4
Operating System: Microsoft Windows 11 Pro 10.0.26200
Bug Description
What you expected to happen:
An unexpected exception raised while parsing an XBRL filing should produce an observable failure signal that identifies the affected filing, rather than being silently converted into an apparently successful partial XBRLS result. That signal could be an exception with filing context, structured failure metadata, or an explicit warning/strict-mode contract; the important behavior is that the caller can detect which requested filing failed and why.
Intentional tolerance for filings that simply have no XBRL data should remain unchanged. A fix can preserve that behavior by explicitly skipping XBRL.from_filing() results of None while making unexpected exceptions observable.
What actually happened:
XBRLS.from_filings() catches every Exception without logging, recording, or re-raising it. The method returns normally with only the successfully parsed filing, so callers cannot tell that one requested filing was lost. The reproduction prints:
returned normally
parsed XBRL count: 1
parsed entities: ['Good Co']
Error message (if any):
No error is emitted. RuntimeError("parser invariant failed") is silently swallowed.
Reproduction
Company/Ticker: N/A (generic exception-handling path; deterministic fault injection is required to prove it reliably)
Time Period: Synthetic FY 2024-2025
Relevant Forms: 10-K (synthetic filing metadata)
Minimal code to reproduce:
from datetime import date
from types import SimpleNamespace
from unittest.mock import patch
from edgar.xbrl import XBRLS
filings = [
SimpleNamespace(
accession_no="good", form="10-K", filing_date=date(2025, 2, 1)
),
SimpleNamespace(
accession_no="broken", form="10-K", filing_date=date(2024, 2, 1)
),
]
def parse(filing):
if filing.accession_no == "broken":
raise RuntimeError("parser invariant failed")
return SimpleNamespace(entity_info={"entity_name": "Good Co"})
with patch("edgar.xbrl.xbrl.XBRL.from_filing", side_effect=parse):
try:
result = XBRLS.from_filings(filings, filter_amendments=False)
except RuntimeError as exc:
print(f"raised RuntimeError: {exc}")
else:
print("returned normally")
print(f"parsed XBRL count: {len(result.xbrl_list)}")
print(
"parsed entities: "
f"{[x.entity_info['entity_name'] for x in result.xbrl_list]}"
)
Additional Context
This was reproduced against EdgarTools 5.53.0 and re-verified on current main at commit 8feb1a8743349caba7b84197794c624c75f27800.
As a retained real-filing negative control, a targeted pinned-5.53.0 manifest exercised 16 structurally suspicious/new cache bundles through XBRL.from_filing(). All 16 calls returned normally: 11 produced parsed facts and five attachment packages produced no instance/facts. None naturally reached the blanket exception path. Because that negative-control set contains no naturally throwing filing, attaching an arbitrary ticker would be misleading. The deterministic injected exception remains the appropriate proof that any unexpected parser failure is silently discarded by the blanket handler.
The parsing loop wraps XBRL.from_filing(filing) in a blanket except Exception: pass and then constructs an XBRLS object from only the successfully accumulated parses (xbrls.py lines 77-86). This makes a parser defect, corrupted attachment, or other unexpected runtime failure indistinguishable from a fully successful multi-filing parse. If every parse raises, the same path returns a seemingly successful empty XBRLS object.
This report is deliberately narrower than #459. That issue established tolerance for old filings with no XBRL data, and XBRL.from_filing() intentionally returns None when a filing has no XBRL attachments (xbrl.py lines 618-650). This report does not request that intentional pre-XBRL/no-XBRL behavior be removed; it concerns only unexpected exceptions that are currently swallowed and converted into an unmarked partial result.
This report also does not request same-company validation. from_filings() already documents that input filings should belong to the same company, and enforcing that precondition would be a separate enhancement.
This issue will be handled using EdgarTools' systematic issue resolution workflow. A reproduction test will be created to verify the fix.
Issue Type
Environment
EdgarTools Version: 5.53.0
Python Version: 3.14.4
Operating System: Microsoft Windows 11 Pro 10.0.26200
Bug Description
What you expected to happen:
An unexpected exception raised while parsing an XBRL filing should produce an observable failure signal that identifies the affected filing, rather than being silently converted into an apparently successful partial
XBRLSresult. That signal could be an exception with filing context, structured failure metadata, or an explicit warning/strict-mode contract; the important behavior is that the caller can detect which requested filing failed and why.Intentional tolerance for filings that simply have no XBRL data should remain unchanged. A fix can preserve that behavior by explicitly skipping
XBRL.from_filing()results ofNonewhile making unexpected exceptions observable.What actually happened:
XBRLS.from_filings()catches everyExceptionwithout logging, recording, or re-raising it. The method returns normally with only the successfully parsed filing, so callers cannot tell that one requested filing was lost. The reproduction prints:Error message (if any):
Reproduction
Company/Ticker: N/A (generic exception-handling path; deterministic fault injection is required to prove it reliably)
Time Period: Synthetic FY 2024-2025
Relevant Forms: 10-K (synthetic filing metadata)
Minimal code to reproduce:
Additional Context
This was reproduced against EdgarTools 5.53.0 and re-verified on current
mainat commit8feb1a8743349caba7b84197794c624c75f27800.As a retained real-filing negative control, a targeted pinned-5.53.0 manifest exercised 16 structurally suspicious/new cache bundles through
XBRL.from_filing(). All 16 calls returned normally: 11 produced parsed facts and five attachment packages produced no instance/facts. None naturally reached the blanket exception path. Because that negative-control set contains no naturally throwing filing, attaching an arbitrary ticker would be misleading. The deterministic injected exception remains the appropriate proof that any unexpected parser failure is silently discarded by the blanket handler.The parsing loop wraps
XBRL.from_filing(filing)in a blanketexcept Exception: passand then constructs anXBRLSobject from only the successfully accumulated parses (xbrls.pylines 77-86). This makes a parser defect, corrupted attachment, or other unexpected runtime failure indistinguishable from a fully successful multi-filing parse. If every parse raises, the same path returns a seemingly successful emptyXBRLSobject.This report is deliberately narrower than #459. That issue established tolerance for old filings with no XBRL data, and
XBRL.from_filing()intentionally returnsNonewhen a filing has no XBRL attachments (xbrl.pylines 618-650). This report does not request that intentional pre-XBRL/no-XBRL behavior be removed; it concerns only unexpected exceptions that are currently swallowed and converted into an unmarked partial result.This report also does not request same-company validation.
from_filings()already documents that input filings should belong to the same company, and enforcing that precondition would be a separate enhancement.This issue will be handled using EdgarTools' systematic issue resolution workflow. A reproduction test will be created to verify the fix.