Skip to content

XBRLS.from_filings() silently swallows unexpected parsing exceptions and returns partial results #1174

Description

@synfonia-llc

Issue Type

  • Data Quality Issue (incorrect financial values, missing data)
  • XBRL Parsing Issue (statement rendering, concept mapping)
  • Filing Access Issue (download failures, attachment problems)
  • Performance Issue (slow operations, memory problems)
  • Compatibility Issue (platform/dependency conflicts)
  • Documentation Issue (unclear instructions, missing examples)

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions