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

Commit

Permalink
Add ODL availability metadata (PP-869) (#170)
Browse files Browse the repository at this point in the history
* Add ODL availability metadata

* Update OPDS2 to handle both time tracking and availability

* Code review feedback
  • Loading branch information
jonathangreen authored Jun 18, 2024
1 parent 7f1a7f5 commit 264cac7
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/webpub_manifest_parser/core/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import jsonschema
from dateutil import parser as datetime_parser
from jsonschema import FormatError
from jsonschema.exceptions import FormatError
from uritemplate import URITemplate

from webpub_manifest_parser.errors import BaseError
Expand Down
10 changes: 9 additions & 1 deletion src/webpub_manifest_parser/odl/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
URIProperty,
)
from webpub_manifest_parser.odl.registry import ODLCollectionRolesRegistry
from webpub_manifest_parser.opds2.ast import OPDS2Feed, OPDS2Price, OPDS2Publication
from webpub_manifest_parser.opds2.ast import (
OPDS2AvailabilityInformation,
OPDS2Feed,
OPDS2Price,
OPDS2Publication,
)
from webpub_manifest_parser.opds2.registry import OPDS2CollectionRolesRegistry
from webpub_manifest_parser.utils import is_string

Expand Down Expand Up @@ -62,6 +67,9 @@ class ODLLicenseMetadata(Node):
nested_type=OPDS2Price,
)
source = URIProperty("source", required=False)
availability = TypeProperty(
"availability", required=False, nested_type=OPDS2AvailabilityInformation
)

def __init__(self, identifier=None, formats=None, created=None):
"""Initialize a new instance of ODLLicenseMetadata class.
Expand Down
22 changes: 20 additions & 2 deletions src/webpub_manifest_parser/opds2/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from webpub_manifest_parser.core.properties import (
ArrayProperty,
BooleanProperty,
DateOrTimeProperty,
DateTimeProperty,
EnumProperty,
Expand Down Expand Up @@ -276,6 +277,8 @@ class OPDS2AvailabilityInformation(Node):
)
since = DateOrTimeProperty("since", required=False)
until = DateOrTimeProperty("until", required=False)
reason = URIProperty("reason", required=False)
detail = StringProperty("detail", required=False)


class OPDS2LinkProperties(LinkProperties):
Expand Down Expand Up @@ -345,21 +348,36 @@ def __init__(
self.number_of_items = number_of_items


class OPDS2PublicationMetadata(PresentationMetadata):
# OPDS2 Removal proposed property. See here for more detail:
# https://github.com/opds-community/drafts/discussions/63
availability = TypeProperty(
"availability", required=False, nested_type=OPDS2AvailabilityInformation
)
# Palace OPDS extension to indicate that the publication supports time tracking
time_tracking = BooleanProperty(
"http://palaceproject.io/terms/timeTracking", False, default_value=False
)


class OPDS2Publication(Collection):
"""OPDS 2.0 publication."""

images = CompactCollectionProperty(
"images", required=True, role=OPDS2CollectionRolesRegistry.IMAGES
)
metadata = TypeProperty(
key="metadata", required=True, nested_type=OPDS2PublicationMetadata
)

def __init__(self, metadata=None, links=None, images=None):
"""Initialize a new instance of OPDS2Publication class."""
super().__init__()

if metadata and not isinstance(metadata, PresentationMetadata):
if metadata and not isinstance(metadata, OPDS2PublicationMetadata):
raise ValueError(
"Argument 'metadata' must be an instance of {}".format(
PresentationMetadata
OPDS2PublicationMetadata
)
)
if links and not isinstance(links, LinkList):
Expand Down
11 changes: 10 additions & 1 deletion tests/files/odl/feed.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
"name": "Juvenile Fiction",
"links": []
}
]
],
"availability": {
"state": "available"
}
},
"links": [
{
Expand Down Expand Up @@ -81,6 +84,12 @@
"copy": false,
"print": false,
"tts": false
},
"availability": {
"state": "unavailable",
"until": "2000-05-04T03:02:01Z",
"detail": "a detailed reason",
"reason": "https://registry.opds.io/reason#exhausted"
}
},
"links": [
Expand Down
12 changes: 11 additions & 1 deletion tests/files/opds2/feed.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
"urn:isbn:978-3-16-148410-0"
],
"language": "en",
"availability": {
"state": "unavailable",
"reason": "https://registry.opds.io/reason#removed",
"detail": "This publication is no longer available in your subscription",
"since": "2019-09-29T01:03:02Z"
},
"http://palaceproject.io/terms/timeTracking": true,
"published": "2015-09-29T00:00:00Z",
"modified": "2015-09-29T17:00:00Z",
"subject": [
Expand Down Expand Up @@ -97,7 +104,10 @@
"href": "http://montreal.pretnumerique.ca/v1/media/EDEN656014-9782404012711-epub/loans_activation.opds2?_resource_id=5f775a8c2357946dbe59f911",
"properties": {
"availability": {
"state": "available"
"state": "available",
"until": "2019-09-29T01:03:02Z",
"detail": "a detailed reason",
"reason": "http://terms.example.org/available"
},
"indirectAcquisition": [
{
Expand Down
9 changes: 5 additions & 4 deletions tests/webpub_manifest_parser/core/test_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
OPDS2Group,
OPDS2Navigation,
OPDS2Publication,
OPDS2PublicationMetadata,
)
from webpub_manifest_parser.rwpm import (
RWPMCollectionRolesRegistry,
Expand Down Expand Up @@ -136,7 +137,7 @@ def check_analyzer_errors(
publications=CollectionList(
[
OPDS2Publication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(title="Publication 1"),
links=LinkList(
[
Link(
Expand Down Expand Up @@ -167,7 +168,7 @@ def check_analyzer_errors(
publications=CollectionList(
[
OPDS2Publication(
metadata=PresentationMetadata(title="Publication 1.1"),
metadata=OPDS2PublicationMetadata(title="Publication 1.1"),
links=LinkList(
[
Link(
Expand Down Expand Up @@ -199,7 +200,7 @@ def check_analyzer_errors(
publications=CollectionList(
[
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(title="Publication 1"),
links=LinkList([Link(href="http://example.com")]),
licenses=CollectionList(
[
Expand All @@ -215,7 +216,7 @@ def check_analyzer_errors(
),
),
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(title="Publication 1"),
links=LinkList([Link(href="http://example.com")]),
licenses=CollectionList(
[
Expand Down
34 changes: 32 additions & 2 deletions tests/webpub_manifest_parser/odl/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
import os
from unittest import TestCase

from dateutil.tz import tzoffset
from dateutil.tz import tzoffset, tzutc

from webpub_manifest_parser.core import ManifestParserResult
from webpub_manifest_parser.odl import ODLFeedParserFactory
from webpub_manifest_parser.opds2.ast import OPDS2FeedMetadata
from webpub_manifest_parser.opds2.ast import (
OPDS2AvailabilityInformation,
OPDS2AvailabilityType,
OPDS2FeedMetadata,
OPDS2PublicationMetadata,
)
from webpub_manifest_parser.opds2.registry import OPDS2LinkRelationsRegistry
from webpub_manifest_parser.utils import first_or_default

Expand Down Expand Up @@ -34,6 +39,15 @@ def test(self):
self.assertEqual(1, len(feed.publications))
[publication] = feed.publications

self.assertIsInstance(publication.metadata, OPDS2PublicationMetadata)
self.assertIsInstance(
publication.metadata.availability, OPDS2AvailabilityInformation
)
self.assertEqual(
OPDS2AvailabilityType.AVAILABLE.value,
publication.metadata.availability.state,
)

self.assertEqual(1, len(publication.licenses))
[license] = publication.licenses

Expand Down Expand Up @@ -70,6 +84,22 @@ def test(self):
self.assertEqual(False, license.metadata.protection.print_allowed)
self.assertEqual(False, license.metadata.protection.tts_allowed)

self.assertIsInstance(
license.metadata.availability, OPDS2AvailabilityInformation
)
self.assertEqual(
OPDS2AvailabilityType.UNAVAILABLE.value, license.metadata.availability.state
)
self.assertEqual(
datetime.datetime(2000, 5, 4, 3, 2, 1, tzinfo=tzutc()),
license.metadata.availability.until,
)
self.assertEqual("a detailed reason", license.metadata.availability.detail)
self.assertEqual(
"https://registry.opds.io/reason#exhausted",
license.metadata.availability.reason,
)

self.assertEqual(2, len(license.links))
borrow_link = first_or_default(
license.links.get_by_rel(OPDS2LinkRelationsRegistry.BORROW.key)
Expand Down
28 changes: 16 additions & 12 deletions tests/webpub_manifest_parser/odl/test_semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
from parameterized import parameterized

from tests.webpub_manifest_parser.core.test_analyzer import AnalyzerTest
from webpub_manifest_parser.core.ast import (
CollectionList,
Link,
LinkList,
PresentationMetadata,
)
from webpub_manifest_parser.core.ast import CollectionList, Link, LinkList
from webpub_manifest_parser.core.registry import LinkRelationsRegistry
from webpub_manifest_parser.core.semantic import (
MANIFEST_MISSING_SELF_LINK_ERROR,
Expand Down Expand Up @@ -40,6 +35,7 @@
OPDS2FeedMetadata,
OPDS2Group,
OPDS2Navigation,
OPDS2PublicationMetadata,
)


Expand Down Expand Up @@ -141,7 +137,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
publications=CollectionList(
[
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
licenses=CollectionList(),
)
]
Expand All @@ -150,7 +148,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
[
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
node=ODLPublication(
metadata=PresentationMetadata(title="Publication 1")
metadata=OPDS2PublicationMetadata(title="Publication 1")
),
node_property=None,
)
Expand All @@ -171,7 +169,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
publications=CollectionList(
[
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
links=LinkList([Link(href="http://example.com")]),
)
]
Expand All @@ -180,7 +180,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
[
ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
node=ODLPublication(
metadata=PresentationMetadata(title="Publication 1")
metadata=OPDS2PublicationMetadata(title="Publication 1")
),
node_property=None,
)
Expand All @@ -201,7 +201,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
publications=CollectionList(
[
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
links=LinkList(
[
Link(
Expand Down Expand Up @@ -233,7 +235,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest):
publications=CollectionList(
[
ODLPublication(
metadata=PresentationMetadata(title="Publication 1"),
metadata=OPDS2PublicationMetadata(
title="Publication 1"
),
links=LinkList([Link(href="http://example.com")]),
licenses=CollectionList(
[
Expand Down
Loading

0 comments on commit 264cac7

Please sign in to comment.