Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
Relax requirement for ODL feeds to have an OA acquisition link (PP-15…
Browse files Browse the repository at this point in the history
…30) (#185)

The spec defines an OPDS 2 + ODL feed as:
- It must be a valid OPDS Feed as defined in OPDS-2 with one difference:
  - The requirement for the presence of an Acquisition Link is relaxed
  - Instead, each Publication listed in publications must either contain a licenses subcollection or an Open-Access Acquisition Link (http://opds-spec.org/acquisition/open-access)

The requirement that each link be an Open-Access Acquisition Link is overly restrictive, and prevents us from importing mixed OPDS2 and OPDS2 + ODL feeds. We relax that requirement to:
  - Instead, each Publication listed in publications must either contain a licenses subcollection or an acquisition Link (link that starts with http://opds-spec.org/acquisition).
  • Loading branch information
jonathangreen authored Aug 1, 2024
1 parent b934f90 commit 2c63dc3
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 10 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ A parser for the
[Open Publication Distribution System 2.0 (OPDS 2.0)](https://drafts.opds.io/opds-2.0), and
[Open Distribution to Libraries 1.0 (ODL)](https://drafts.opds.io/odl-1.0.html) formats.

**Note**: This parser varys from the OPDS 2 + ODL spec in that it allows OPDS 2 + ODL feeds to contain
non-open access acquisition links.

The spec [defines](https://drafts.opds.io/odl-1.0.html#21-opds-20) an OPDS 2 + ODL feed as:

- It must be a valid OPDS Feed as defined in [[OPDS-2](https://drafts.opds.io/odl-1.0.html#normative-references)] with
one difference:
- The requirement for the presence of an Acquisition Link is relaxed
- Instead, each Publication listed in publications must either contain a licenses subcollection or an Open-Access
Acquisition Link (http://opds-spec.org/acquisition/open-access)

The requirement that each link be an Open-Access Acquisition Link is overly restrictive, and prevents us from importing
mixed OPDS2 and OPDS2 + ODL feeds. We relax the requirement to:

- It must be a valid OPDS Feed as defined in [[OPDS-2](https://drafts.opds.io/odl-1.0.html#normative-references)] with
one difference:
- The requirement for the presence of an Acquisition Link is relaxed
- Instead, each Publication listed in publications must either contain a licenses subcollection or an
**Acquisition Link** (http://opds-spec.org/acquisition)

## Usage

Install the library with `pip`
Expand Down
17 changes: 12 additions & 5 deletions src/webpub_manifest_parser/odl/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ def _format_message(
message="ODL feed '{0}' contains redundant 'navigation' subcollection",
)

ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR = partial(
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR = partial(
ODLPublicationSemanticError,
message="ODL publication '{0}' contains neither 'licenses' subcollection nor "
"an Open-Access Acquisition Link (http://opds-spec.org/acquisition/open-access)",
"an Acquisition Link (http://opds-spec.org/acquisition)",
)

ODL_LICENSE_MUST_CONTAIN_SELF_LINK_TO_LICENSE_INFO_DOCUMENT_ERROR = partial(
Expand Down Expand Up @@ -195,12 +195,19 @@ def visit(self, node):
"""
self._logger.debug(f"Started processing {encode(node)}")

links = node.links or []
acquisition_uri = OPDS2LinkRelationsRegistry.ACQUISITION.key
acquisition_links = [
l
for l in links
if any([rel.startswith(acquisition_uri) for rel in l.rels or []])
]

if (not node.licenses or len(node.licenses) == 0) and (
(not node.links or len(node.links) == 0)
or not node.links.get_by_rel(OPDS2LinkRelationsRegistry.OPEN_ACCESS.key)
(not node.links or len(node.links) == 0) or (len(acquisition_links) == 0)
):
with self._record_errors():
raise ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
raise ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR(
node=node, node_property=None
)
elif node.licenses:
Expand Down
78 changes: 73 additions & 5 deletions tests/webpub_manifest_parser/odl/test_semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR,
ODL_LICENSE_MUST_CONTAIN_CHECKOUT_LINK_TO_LICENSE_STATUS_DOCUMENT_ERROR,
ODL_LICENSE_MUST_CONTAIN_SELF_LINK_TO_LICENSE_INFO_DOCUMENT_ERROR,
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR,
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR,
ODLSemanticAnalyzer,
)
from webpub_manifest_parser.opds2 import (
Expand Down Expand Up @@ -123,7 +123,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
],
),
(
"when_publication_does_not_contain_neither_licenses_nor_oa_link",
"when_publication_contains_neither_licenses_nor_acquisition_link",
ODLFeed(
metadata=OPDS2FeedMetadata(title="test"),
links=LinkList(
Expand All @@ -146,7 +146,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
),
),
[
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR(
node=ODLPublication(
metadata=OPDS2PublicationMetadata(title="Publication 1")
),
Expand All @@ -155,7 +155,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
],
),
(
"when_publication_does_not_contain_neither_licenses_nor_oa_link",
"when_publication_contains_neither_licenses_nor_acquisition_link",
ODLFeed(
metadata=OPDS2FeedMetadata(title="test"),
links=LinkList(
Expand All @@ -178,7 +178,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
),
),
[
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR(
node=ODLPublication(
metadata=OPDS2PublicationMetadata(title="Publication 1")
),
Expand Down Expand Up @@ -220,6 +220,74 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
),
[],
),
(
"when_publication_does_not_contain_licenses_and_has_an_acquisition_link",
ODLFeed(
metadata=OPDS2FeedMetadata(title="test"),
links=LinkList(
[
Link(
href="http://example.com",
rels=[LinkRelationsRegistry.SELF.key],
)
]
),
publications=CollectionList(
[
ODLPublication(
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
links=LinkList(
[
Link(
href="http://example.com",
rels=[
OPDS2LinkRelationsRegistry.ACQUISITION.key
],
)
]
),
)
]
),
),
[],
),
(
"when_publication_does_not_contain_licenses_and_has_an_unknown_acquisition_link",
ODLFeed(
metadata=OPDS2FeedMetadata(title="test"),
links=LinkList(
[
Link(
href="http://example.com",
rels=[LinkRelationsRegistry.SELF.key],
)
]
),
publications=CollectionList(
[
ODLPublication(
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
links=LinkList(
[
Link(
href="http://example.com",
rels=[
f"{OPDS2LinkRelationsRegistry.ACQUISITION.key}/unknown"
],
)
]
),
)
]
),
),
[],
),
(
"when_license_does_not_contain_self_link_and_borrow_link",
ODLFeed(
Expand Down

0 comments on commit 2c63dc3

Please sign in to comment.