diff --git a/src/webpub_manifest_parser/odl/ast.py b/src/webpub_manifest_parser/odl/ast.py index 204e652..5ad709e 100644 --- a/src/webpub_manifest_parser/odl/ast.py +++ b/src/webpub_manifest_parser/odl/ast.py @@ -6,7 +6,6 @@ Collection, CollectionList, Node, - PresentationMetadata, ) from webpub_manifest_parser.core.properties import ( ArrayOfStringsProperty, @@ -114,18 +113,9 @@ def __hash__(self): return hash((self.metadata, self.links)) -class ODLPublicationMetadata(PresentationMetadata): - availability = TypeProperty( - "availability", required=False, nested_type=OPDS2AvailabilityInformation - ) - - class ODLPublication(OPDS2Publication): """ODL publication.""" - metadata = TypeProperty( - key="metadata", required=True, nested_type=ODLPublicationMetadata - ) links = ArrayOfLinksProperty(key="links", required=False) licenses = ArrayOfCollectionsProperty( "licenses", diff --git a/src/webpub_manifest_parser/opds2/ast.py b/src/webpub_manifest_parser/opds2/ast.py index 6da9dd0..2d76318 100644 --- a/src/webpub_manifest_parser/opds2/ast.py +++ b/src/webpub_manifest_parser/opds2/ast.py @@ -21,6 +21,7 @@ ) from webpub_manifest_parser.core.properties import ( ArrayProperty, + BooleanProperty, DateOrTimeProperty, DateTimeProperty, EnumProperty, @@ -347,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): diff --git a/tests/files/opds2/feed.json b/tests/files/opds2/feed.json index 9ce9d82..08ded1d 100644 --- a/tests/files/opds2/feed.json +++ b/tests/files/opds2/feed.json @@ -23,6 +23,13 @@ "urn:isbn:978-3-16-148410-0" ], "language": "en", + "availability": { + "state": "unavailable", + "reason": "https://example.com/subscription/removal", + "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": [ diff --git a/tests/webpub_manifest_parser/core/test_analyzer.py b/tests/webpub_manifest_parser/core/test_analyzer.py index 5729fc7..afdbb89 100644 --- a/tests/webpub_manifest_parser/core/test_analyzer.py +++ b/tests/webpub_manifest_parser/core/test_analyzer.py @@ -29,6 +29,7 @@ OPDS2Group, OPDS2Navigation, OPDS2Publication, + OPDS2PublicationMetadata, ) from webpub_manifest_parser.rwpm import ( RWPMCollectionRolesRegistry, @@ -136,7 +137,7 @@ def check_analyzer_errors( publications=CollectionList( [ OPDS2Publication( - metadata=PresentationMetadata(title="Publication 1"), + metadata=OPDS2PublicationMetadata(title="Publication 1"), links=LinkList( [ Link( @@ -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( @@ -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( [ @@ -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( [ diff --git a/tests/webpub_manifest_parser/odl/test_parser.py b/tests/webpub_manifest_parser/odl/test_parser.py index 90b61e0..b4c779b 100644 --- a/tests/webpub_manifest_parser/odl/test_parser.py +++ b/tests/webpub_manifest_parser/odl/test_parser.py @@ -6,11 +6,11 @@ from webpub_manifest_parser.core import ManifestParserResult from webpub_manifest_parser.odl import ODLFeedParserFactory -from webpub_manifest_parser.odl.ast import ODLPublicationMetadata 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 @@ -39,7 +39,7 @@ def test(self): self.assertEqual(1, len(feed.publications)) [publication] = feed.publications - self.assertIsInstance(publication.metadata, ODLPublicationMetadata) + self.assertIsInstance(publication.metadata, OPDS2PublicationMetadata) self.assertIsInstance( publication.metadata.availability, OPDS2AvailabilityInformation ) diff --git a/tests/webpub_manifest_parser/odl/test_semantic.py b/tests/webpub_manifest_parser/odl/test_semantic.py index db53a72..49afe8c 100644 --- a/tests/webpub_manifest_parser/odl/test_semantic.py +++ b/tests/webpub_manifest_parser/odl/test_semantic.py @@ -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, @@ -40,6 +35,7 @@ OPDS2FeedMetadata, OPDS2Group, OPDS2Navigation, + OPDS2PublicationMetadata, ) @@ -141,7 +137,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): publications=CollectionList( [ ODLPublication( - metadata=PresentationMetadata(title="Publication 1"), + metadata=OPDS2PublicationMetadata( + title="Publication 1" + ), licenses=CollectionList(), ) ] @@ -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, ) @@ -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")]), ) ] @@ -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, ) @@ -201,7 +201,9 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): publications=CollectionList( [ ODLPublication( - metadata=PresentationMetadata(title="Publication 1"), + metadata=OPDS2PublicationMetadata( + title="Publication 1" + ), links=LinkList( [ Link( @@ -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( [ diff --git a/tests/webpub_manifest_parser/opds2/test_parser.py b/tests/webpub_manifest_parser/opds2/test_parser.py index 528f506..56ce297 100644 --- a/tests/webpub_manifest_parser/opds2/test_parser.py +++ b/tests/webpub_manifest_parser/opds2/test_parser.py @@ -20,6 +20,7 @@ OPDS2Feed, OPDS2FeedMetadata, OPDS2LinkProperties, + OPDS2PublicationMetadata, ) from webpub_manifest_parser.opds2.registry import ( OPDS2LinkRelationsRegistry, @@ -61,7 +62,7 @@ def test(self): self.assertEqual(2, len(feed.publications)) publication = feed.publications[0] - self.assertIsInstance(publication.metadata, PresentationMetadata) + self.assertIsInstance(publication.metadata, OPDS2PublicationMetadata) self.assertEqual("http://schema.org/Book", publication.metadata.type) self.assertEqual("Moby-Dick", publication.metadata.title) self.assertEqual( @@ -78,6 +79,26 @@ def test(self): datetime.datetime(2015, 9, 29, 17, 0, tzinfo=tzutc()), publication.metadata.modified, ) + self.assertIsInstance( + publication.metadata.availability, OPDS2AvailabilityInformation + ) + self.assertEqual( + publication.metadata.availability.state, + OPDS2AvailabilityType.UNAVAILABLE.value, + ) + self.assertEqual( + publication.metadata.availability.since, + datetime.datetime(2019, 9, 29, 1, 3, 2, tzinfo=tzutc()), + ) + self.assertEqual( + publication.metadata.availability.detail, + "This publication is no longer available in your subscription", + ) + self.assertEqual( + publication.metadata.availability.reason, + "https://example.com/subscription/removal", + ) + self.assertIs(publication.metadata.time_tracking, True) self.assertIsInstance(publication.links, list) self.assertEqual(len(publication.links), 2) @@ -163,6 +184,9 @@ def test(self): publication.metadata.modified, ) + self.assertIs(publication.metadata.availability, None) + self.assertIs(publication.metadata.time_tracking, False) + self.assertIsInstance(publication.links, list) publication_acquisition_link = first_or_default( diff --git a/tests/webpub_manifest_parser/opds2/test_semantic.py b/tests/webpub_manifest_parser/opds2/test_semantic.py index 258bb91..d253c93 100644 --- a/tests/webpub_manifest_parser/opds2/test_semantic.py +++ b/tests/webpub_manifest_parser/opds2/test_semantic.py @@ -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.semantic import SemanticAnalyzerError from webpub_manifest_parser.opds2.ast import ( OPDS2Feed, @@ -16,6 +11,7 @@ OPDS2Group, OPDS2Navigation, OPDS2Publication, + OPDS2PublicationMetadata, ) from webpub_manifest_parser.opds2.registry import ( OPDS2CollectionRolesRegistry, @@ -91,7 +87,9 @@ class OPDS2SemanticAnalyzerTest(AnalyzerTest): publications=CollectionList( [ OPDS2Publication( - metadata=PresentationMetadata(title="Publication 1"), + metadata=OPDS2PublicationMetadata( + title="Publication 1" + ), links=LinkList([Link(href="http://example.com")]), ) ] @@ -127,7 +125,7 @@ class OPDS2SemanticAnalyzerTest(AnalyzerTest): publications=CollectionList( [ OPDS2Publication( - metadata=PresentationMetadata( + metadata=OPDS2PublicationMetadata( title="Publication 1" ), links=LinkList( @@ -192,7 +190,7 @@ def test_semantic_analyzer_does_correctly_processes_valid_ast(self): publications=CollectionList( [ OPDS2Publication( - metadata=PresentationMetadata(title="Publication 1"), + metadata=OPDS2PublicationMetadata(title="Publication 1"), links=LinkList( [ Link( @@ -223,7 +221,7 @@ def test_semantic_analyzer_does_correctly_processes_valid_ast(self): publications=CollectionList( [ OPDS2Publication( - metadata=PresentationMetadata( + metadata=OPDS2PublicationMetadata( title="Publication 1.1" ), links=LinkList(