Skip to content

4.0.0#58

Merged
dhvcc merged 20 commits into
masterfrom
4.0.0
Jul 24, 2026
Merged

4.0.0#58
dhvcc merged 20 commits into
masterfrom
4.0.0

Conversation

@dhvcc

@dhvcc dhvcc commented Jul 23, 2026

Copy link
Copy Markdown
Owner

No description provided.

dhvcc added 18 commits July 23, 2026 00:24
Pydantic v1 compatibility models under rss_parser.models.legacy and the
rss_parser.pydantic_proxy re-export are gone. They were already broken on
Python 3.14. Users who need pydantic v1 should stay on rss-parser 3.x.
RSS 2.0 spec fixes:
- All <item> elements are now optional; a validator enforces the one spec
  rule (title or description must be present). Feeds with description-only
  items no longer fail validation
- <skipHours>/<skipDays> are proper models with hour/day lists instead of
  Tag[str] that crashed on any feed using them
- <textInput> is typed as the TextInput model and <rating> as a string
  (they were swapped)
- Added the missing <managingEditor> field
- item.pubDate parses to datetime, ttl to int

Extensibility:
- Channel/RSS/Feed/Atom are generic (PEP 696 defaults), so a custom item
  schema plugs in with one parametrization: RSS[Channel[MyItem]]
- Unknown tags are kept in model_extra (extra='allow') instead of being
  silently dropped
- Models accept population by field name as well as alias
- atom Entry.source is typed as Source instead of str
- Required/Optional mixin classes are flattened into their models
- str(tag) returns the content ('' for self-closing tags), so print() shows
  the value instead of the model repr
- bool(tag) is False for a tag with no content and no attributes
- Attribute access on an empty (self-closing) tag raises a clear
  AttributeError pointing to .attributes instead of
  'NoneType has no attribute ...'
ITunesItemMixin/ITunesChannelMixin expose the itunes:* tags as typed fields,
and the ready-made ITunesItem/ITunesChannel/Podcast models compose them with
the RSS 2.0 schema. Parsed via PodcastParser or
parse(data, parsers={FeedType.RSS: PodcastParser}).
RDF/RDFChannel/RDFItem cover the RSS 1.0 structure where items are siblings
of the channel. Module tags (dc:*, syn:*) are kept in model_extra.
- parse(data) detects RSS 0.9x/2.0, Atom 1.0 or RSS 1.0 (RDF) from the root
  element and returns the matching typed model
- detect_feed_type(data) exposes detection on its own via the FeedType enum
- UnknownFeedTypeError lists the supported roots when detection fails
- New parsers: RDFParser (namespace-prefix tolerant) and PodcastParser
- BaseParser.parse_dict() allows parsing an existing xmltodict mapping
  without serializing back to XML
- README rewritten around the 4.0 API with verified example output
- mkdocs-material site: quickstart, parsing guide, schema customization,
  podcasts, xml handling internals, migration guide, contributing
- Deploy workflow publishes to gh-pages on docs changes in master,
  replacing the stale v1-era page
- pydantic >=2.7 (generic model defaults), explicit typing-extensions dep,
  xmltodict constraint relaxed to >=0.13
- types-xmltodict moved to dev deps; optional docs dependency group
- ruff config migrated to the lint.* sections, py39 targets
- CI runs package doctests and skips runs for docs-only changes
- Samples reorganized into tests/samples/<kind>/<name>/ and auto-discovered;
  a new sample dir is picked up without touching test code
- Opaque result.pkl snapshots replaced with reviewable result.json
  (regenerate with: python -m scripts.update_snapshots)
- New samples: RSS 0.91 and RSS 1.0 (RDF)
- New suites: spec compliance, extensibility/generics, itunes,
  feed detection, Tag ergonomics
- pyproject converted to PEP 621 [project] metadata with hatchling as the
  build backend; dev/docs deps are PEP 735 dependency groups (dev installs
  by default, docs on demand)
- poetry.lock/poetry.toml replaced by uv.lock
- CI, docs and PyPI publish workflows use astral-sh/setup-uv with caching;
  publish builds with 'uv build' and uploads with 'uv publish'
- pre-commit hooks and contributing docs updated to 'uv run'
- xmltodict updated to 1.0.x per the existing >=0.13 constraint; test suite
  verified against it on Python 3.9 and 3.13
Also migrate pre-commit config to non-deprecated stage names.
Both found by the new test layers:
- json_plain()/dict_plain() never flattened anything: pydantic's fallback=
  is only invoked for unserializable objects and Tag is a model, so the
  encoder never ran (broken the same way in 3.x). Reimplemented as a
  parallel walk of the model and its dump; flatten_tag_encoder is removed
- Tag.pre_convert now recognizes the {content, attributes} dump shape, so
  model_validate(model_dump()) round-trips instead of double-wrapping
Malformed input used to leak xmltodict's ExpatError. All parsers and
detect_feed_type now raise InvalidXMLError (a ValueError subclass) with
the original error chained as __cause__.
The Atom spec requires feed/entry <updated>, but YouTube channel feeds have
no feed-level <updated> at all. Spec purity loses to Google-scale reality;
id and title stay required.
Layered test architecture (51 -> 112 tests):
- tests/corpus: 11 feeds captured from the wild (BBC, NPR, heise, HN, xkcd,
  GitHub, The Verge, YouTube, Slashdot RDF in ISO-8859-1, Lex Fridman
  podcast). Expectations in expect.json are derived by inspecting the raw
  XML, never by running the parser, so the parser is checked against the
  documents rather than against itself
- property-based tests (hypothesis): text round-trips, lists are always
  lists, unknown tags are never dropped, validate(dump()) is idempotent
- date fallback tests (RFC 822, ISO 8601, junk kept as string)
- coverage floor enforced in CI (98%, currently 98.6%)
@dhvcc dhvcc self-assigned this Jul 23, 2026
@dhvcc

dhvcc commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

@codex review. Skip md and docs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ece30b5c79

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rss_parser/_parser.py Outdated
Comment thread rss_parser/models/__init__.py Outdated
Comment thread rss_parser/models/__init__.py Outdated
Comment thread rss_parser/models/__init__.py Outdated
dhvcc added 2 commits July 24, 2026 01:48
A valid Atom document may bind the Atom namespace to a prefix
(<atom:feed xmlns:atom="...">), in which case xmltodict keys the root
as "atom:feed" and every Atom element carries the prefix. Both
detect_feed_type() and the universal parse() raised
UnknownFeedTypeError for such documents, and AtomParser could not
validate them.

Detect roots ending in ":feed" as Atom (mirroring the existing
":rdf" handling) and normalize the prefix away in
AtomParser.parse_dict before validating the schema, leaving
foreign-namespace elements untouched.
- dict_plain(by_alias=True) walked the dump using python field names
  while the dump is keyed by aliases (@Version, pubDate, item), so
  every renamed field was silently dropped. Index the dump with the
  field's serialization key instead.
- Tag flattening required the dump to have exactly {content, attributes}
  keys, but options like exclude_defaults drop default fields, leaving
  tags unflattened. Flatten any subset of the Tag shape.
- json_plain() forwarded all kwargs to model_dump(), so JSON encoder
  options like indent=2 raised TypeError. Accept indent/separators/
  sort_keys/ensure_ascii explicitly and pass them to json.dumps,
  sending the rest to dict_plain.
- Tag.pre_convert now accepts partial Tag-shaped dicts (subset match,
  with a dict-typed attributes guard) so that
  model_validate(model_dump(exclude_defaults=True)) round-trips.
Repository owner deleted a comment from alexey-inno Jul 24, 2026
Repository owner deleted a comment from alexey-inno Jul 24, 2026
Repository owner deleted a comment from alexey-inno Jul 24, 2026
@dhvcc
dhvcc marked this pull request as ready for review July 24, 2026 10:35
@dhvcc
dhvcc merged commit e1306cd into master Jul 24, 2026
18 checks passed
Repository owner deleted a comment from alexey-inno Jul 24, 2026
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.

1 participant