Skip to content

Return an empty enclosures list for items with no link (fixes #340)#569

Open
gaoflow wants to merge 1 commit into
kurtmckee:mainfrom
gaoflow:fix-340-enclosures-no-link
Open

Return an empty enclosures list for items with no link (fixes #340)#569
gaoflow wants to merge 1 commit into
kurtmckee:mainfrom
gaoflow:fix-340-enclosures-no-link

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 16, 2026

Copy link
Copy Markdown

Fixes #340.

You agreed back in 2023 that this should not crash; since the issue is still open (and was bumped again in 2024) I put together a fix. Happy to defer if you would rather take it from here.

The bug

Accessing enclosures (or license) on a feed or entry that has an id but no link crashes instead of returning an empty list:

import feedparser
d = feedparser.parse(
    '<feed xmlns="http://www.w3.org/2005/Atom"><entry><id>urn:x</id></entry></feed>'
)
d.entries[0].enclosures      # AttributeError: object has no attribute 'enclosures'
d.entries[0]['enclosures']   # KeyError: 'links'

Reproduced on main (6.0.12) for both Atom and RSS. The documented contract is that enclosures is always a list.

Cause

The computed enclosures and license keys in feedparser/util.py iterate over dict.__getitem__(self, "links"). The links key is only created lazily (setdefault("links", [])) when a <link> is actually parsed, so an entry or feed with no link never gets it, and the iteration raises KeyError: 'links' (surfaced as AttributeError on attribute access).

Fix

Seed an empty links list when an entry (_base.py _start_item) or the feed (mixin.py.__init__) is created. enclosures then returns [] and license is simply absent, matching the docs. Bare FeedParserDict() instances are deliberately left untouched, so test_empty (which asserts enclosures/license are absent on an empty dict) still holds.

The util.py one-liner dict.get(self, "links", []) would be simpler, but it breaks test_empty because it cannot distinguish a parsed-but-link-less object from a bare dict. Seeding at parse time preserves that distinction.

Scope note

The issue is about entries, but the identical crash exists at the feed level (feed.license / feed.enclosures on a feed with no link), so I fixed both. If you would prefer to keep this scoped to the entry case in #340, I am happy to drop the mixin.py change and its fixture.

Tests

Added three well-formed fixtures (an Atom entry, an RSS item, and a feed, each with an id/guid but no link) asserting enclosures == []. Without the fix these fail with KeyError: 'links'; with it the full suite is green (4303 passed, 8 skipped).

Disclosure: I prepared this fix with AI assistance under my direction; I reviewed and verified the change and the tests myself.

The computed "enclosures" and "license" keys iterate over the "links"
key, which is only created lazily when a link is parsed. A feed or entry
that has an id but no link therefore had no "links" key, so accessing
enclosures raised KeyError (surfaced as AttributeError on attribute
access) instead of returning an empty list as documented.

Seed an empty "links" list when an entry or the feed is created so
enclosures returns [] and license is absent. Bare FeedParserDict
instances are untouched, preserving the documented behaviour on an
empty dict.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enclosures property does not exist if entry has an id but no link

1 participant