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

Add ODL availability metadata (PP-869) #170

Merged
merged 3 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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