Skip to content

Comments

Add format-neutral LinkContentType enum for OPDS feed links (PP-3675)#3065

Open
jonathangreen wants to merge 7 commits intomainfrom
chore/format-neutral-link-content-types
Open

Add format-neutral LinkContentType enum for OPDS feed links (PP-3675)#3065
jonathangreen wants to merge 7 commits intomainfrom
chore/format-neutral-link-content-types

Conversation

@jonathangreen
Copy link
Member

Description

Add a LinkContentType enum that replaces hardcoded OPDS1 content types in annotators with format-neutral semantic values. Each serializer maps these to its format-specific content type, making the intermediate representation (annotator → serializer) truly format-neutral.

Changes:

  • feed/types.py: Add LinkContentType enum with OPDS_FEED and OPDS_ENTRY members
  • feed/annotator/circulation.py: Replace all OPDSFeed.ENTRY_TYPE / OPDSFeed.ACQUISITION_FEED_TYPE references with LinkContentType values across permalink_for(), borrow_link(), annotate_work_entry(), add_author_links(), add_series_link(), and annotate_feed()
  • feed/annotator/loan_and_hold.py: Standardize profile link rel from Palace-specific "http://librarysimplified.org/terms/rel/user-profile" to standard "profile"
  • feed/serializer/opds.py: Add _resolve_type() and _resolve_rel() to BaseOPDS1Serializer that map semantic values back to OPDS1-specific types/rels during serialization
  • feed/serializer/opds2.py: Add _resolve_type() to OPDS2Serializer that maps semantic values to OPDS2-specific types (application/opds+json, application/opds-publication+json)
LinkContentType OPDS1 OPDS2
OPDS_FEED application/atom+xml;profile=opds-catalog;kind=acquisition application/opds+json
OPDS_ENTRY application/atom+xml;type=entry;profile=opds-catalog application/opds-publication+json

Motivation and Context

The feed annotator previously embedded OPDS1-specific content types throughout, making OPDS2 a second-class citizen that had to remap OPDS1 conventions. This change makes the intermediate representation format-neutral so neither OPDS1 nor OPDS2 is privileged. This is groundwork for aligning OPDS2 feeds with the OPDS 2.0 Borrowing Best Practices specification.

How Has This Been Tested?

  • All existing feed and OPDS tests pass (235 tests across tests/manager/feed/ and tests/manager/opds/)
  • Added new unit tests for _resolve_type() and _resolve_rel() in both serializers
  • Added integration tests verifying LinkContentType values are correctly resolved during link serialization
  • Verified with mypy that all type annotations are correct

Checklist

  • I have updated the documentation accordingly.
  • All new and existing tests passed.

Replace hardcoded OPDS1 content types in annotators with semantic
LinkContentType values (OPDS_FEED, OPDS_ENTRY) so serializers can
map them to the correct format-specific types. This makes the
intermediate representation format-neutral, treating OPDS1 and OPDS2
as equal peers rather than privileging OPDS1 conventions.

- Add LinkContentType StrEnum to feed/types.py
- Update annotators to use LinkContentType instead of OPDSFeed constants
- Standardize profile link rel from Palace-specific to "profile"
- Add _resolve_type()/_resolve_rel() to OPDS1 serializer
- Add _resolve_type() to OPDS2 serializer
@codecov
Copy link

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 96.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.18%. Comparing base (22d454e) to head (e919f32).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/palace/manager/feed/serializer/opds.py 96.55% 0 Missing and 1 partial ⚠️
src/palace/manager/feed/serializer/opds2.py 93.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3065   +/-   ##
=======================================
  Coverage   93.18%   93.18%           
=======================================
  Files         489      489           
  Lines       44943    44971   +28     
  Branches     6191     6195    +4     
=======================================
+ Hits        41880    41908   +28     
+ Misses       1986     1985    -1     
- Partials     1077     1078    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The enum values are opaque identifiers, not meaningful strings.
Widen type annotations with a LinkType alias (str | LinkContentType)
so the type system enforces that serializers resolve enum values
before emitting output.
Match the pattern used by _serialize_link: resolve type and rel
from the link's fields up front instead of mutating the TypedDict
returned by link_attribs().
Remove Link.link_attribs() and its LinkAttributes TypedDict since
serializers now resolve fields directly. Document why OPDS1 needs
_REL_MAP while OPDS2 does not.
@jonathangreen jonathangreen marked this pull request as ready for review February 20, 2026 14:27
@jonathangreen jonathangreen requested review from a team February 20, 2026 14:27
Copy link
Contributor

@tdilauro tdilauro left a comment

Choose a reason for hiding this comment

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

Nice! 🚀



#: Union type for link type fields that accept either concrete MIME types or
#: semantic :class:`LinkContentType` values resolved at serialization time.
Copy link
Contributor

Choose a reason for hiding this comment

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

I had to go look up this format. I didn't realize there was a way to document variables like this. 🤷🏽‍♂️ 🚀

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.

2 participants