Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

views: FAIR signposting level 1 support & remove linkset link to itself #1908

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 6 additions & 2 deletions invenio_rdm_records/resources/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020, 2021 CERN.
# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2020 Northwestern University.
# Copyright (C) 2021 Graz University of Technology.
#
Expand Down Expand Up @@ -34,7 +34,10 @@
)
from .marcxml import MARCXMLSerializer
from .schemaorg import SchemaorgJSONLDSerializer
from .signposting import FAIRSignpostingProfileLvl2Serializer
from .signposting import (
FAIRSignpostingProfileLvl1Serializer,
FAIRSignpostingProfileLvl2Serializer,
)
from .ui import UIJSONSerializer

__all__ = (
Expand All @@ -47,6 +50,7 @@
"DataPackageSerializer",
"DublinCoreJSONSerializer",
"DublinCoreXMLSerializer",
"FAIRSignpostingProfileLvl1Serializer",
"FAIRSignpostingProfileLvl2Serializer",
"GeoJSONSerializer",
"IIIFCanvasV2JSONSerializer",
Expand Down
31 changes: 29 additions & 2 deletions invenio_rdm_records/resources/serializers/signposting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 Northwestern University.
# Copyright (C) 2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.

"""Signposting serializers."""

from flask_resources import BaseListSchema, MarshmallowSerializer
from flask_resources.serializers import JSONSerializer
from flask_resources.serializers import JSONSerializer, SimpleSerializer

from .schema import FAIRSignpostingProfileLvl2Schema
from .schema import FAIRSignpostingProfileLvl2Schema, LandingPageLvl1Schema


class FAIRSignpostingProfileLvl1Serializer(MarshmallowSerializer):
"""FAIR Signposting Profile level 1 serializer."""

def __init__(self):
"""Initialise Serializer."""
super().__init__(
format_serializer_cls=SimpleSerializer,
object_schema_cls=LandingPageLvl1Schema,
list_schema_cls=BaseListSchema,
encoder=self.fair_signposting_tostring,
)

@classmethod
def fair_signposting_tostring(cls, record):
"""Stringify a FAIR Signposting record."""
links = []
for rel, values in record.items():
# if rel not in excluded_keys:
for value in values:
link = f'<{value["href"]}> ; rel="{rel}"'
if "type" in value:
link += f' ; type="{value["type"]}"'
links.append(link)
return " , ".join(links)


class FAIRSignpostingProfileLvl2Serializer(MarshmallowSerializer):
Expand Down
41 changes: 35 additions & 6 deletions invenio_rdm_records/resources/serializers/signposting/schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 Northwestern University.
# Copyright (C) 2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
Expand All @@ -20,18 +21,13 @@ class LandingPageSchema(Schema):
Serialization input (`obj`) is a whole record dict projection.
"""

anchor = fields.Method(serialize="serialize_anchor")
author = fields.Method(serialize="serialize_author")
cite_as = fields.Method(data_key="cite-as", serialize="serialize_cite_as")
describedby = fields.Method(serialize="serialize_describedby")
item = fields.Method(serialize="serialize_item")
license = fields.Method(serialize="serialize_license")
type = fields.Method(serialize="serialize_type")

def serialize_anchor(self, obj, **kwargs):
"""Seralize to landing page URL."""
return obj["links"]["self_html"]

def serialize_author(self, obj, **kwargs):
"""Serialize author(s).

Expand Down Expand Up @@ -75,6 +71,8 @@ def serialize_describedby(self, obj, **kwargs):
result = [
{"href": obj["links"]["self"], "type": mimetype}
for mimetype in sorted(record_serializers)
# Remove the linkset serializer, so that the linkset does not link to itself.
if mimetype != "application/linkset+json"
]

return result or missing
Expand Down Expand Up @@ -142,6 +140,37 @@ def serialize_type(self, obj, **kwargs):
return result


class LandingPageLvl1Schema(LandingPageSchema):
"""Schema for serialization of link context object for the level 1 landing page.

Serialization input (`obj`) is a whole record dict projection.
"""

linkset = fields.Method(serialize="serialize_linkset")

def serialize_linkset(self, obj, **kwargs):
"""Serialize the linkset URL."""
return [
{
"href": obj["links"]["self"],
"type": "application/linkset+json",
}
]


class LandingPageLvl2Schema(LandingPageSchema):
"""Schema for serialization of link context object for the level 2 landing page.

Serialization input (`obj`) is a whole record dict projection.
"""

anchor = fields.Method(serialize="serialize_anchor")

def serialize_anchor(self, obj, **kwargs):
"""Serialize to landing page URL."""
return obj["links"]["self_html"]


class ContentResourceSchema(Schema):
"""Schema for serialization of link context object for the content resource.

Expand Down Expand Up @@ -203,7 +232,7 @@ class FAIRSignpostingProfileLvl2Schema(Schema):

def serialize_linkset(self, obj, **kwargs):
"""Serialize linkset."""
result = [LandingPageSchema().dump(obj)]
result = [LandingPageLvl2Schema().dump(obj)]

content_resource_schema = ContentResourceSchema(context={"record_dict": obj})
result += [
Expand Down
81 changes: 73 additions & 8 deletions tests/resources/serializers/test_signposting_serializer.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 Northwestern University.
# Copyright (C) 2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.

"""Resources serializers tests."""

from invenio_rdm_records.resources.serializers import (
FAIRSignpostingProfileLvl1Serializer,
FAIRSignpostingProfileLvl2Serializer,
)

Expand All @@ -33,10 +35,6 @@ def test_signposting_serializer_full(running_app, full_record_to_dict):
"href": "https://127.0.0.1:5000/api/records/12345-abcde",
"type": "application/ld+json",
},
{
"href": "https://127.0.0.1:5000/api/records/12345-abcde",
"type": "application/linkset+json",
},
{
"href": "https://127.0.0.1:5000/api/records/12345-abcde",
"type": "application/marcxml+xml",
Expand Down Expand Up @@ -125,6 +123,43 @@ def test_signposting_serializer_full(running_app, full_record_to_dict):
assert expected == serialized


def test_signposting_lvl1_serializer_full(running_app, full_record_to_dict):
ui_url = "https://127.0.0.1:5000/records/12345-abcde"
api_url = "https://127.0.0.1:5000/api/records/12345-abcde"
filename = "test.txt"

expected = [
f'<https://orcid.org/0000-0001-8135-3489> ; rel="author"',
f'<https://doi.org/10.1234/12345-abcde> ; rel="cite-as"',
f'<{api_url}> ; rel="describedby" ; type="application/dcat+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/json"',
f'<{api_url}> ; rel="describedby" ; type="application/ld+json"',
f'<{api_url}> ; rel="describedby" ; type="application/marcxml+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.citationstyles.csl+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.geo+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.full+csv"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.simple+csv"',
f'<{api_url}> ; rel="describedby" ; type="application/x-bibtex"',
f'<{api_url}> ; rel="describedby" ; type="application/x-dc+xml"',
f'<{api_url}> ; rel="describedby" ; type="text/x-bibliography"',
f'<{ui_url}/files/{filename}> ; rel="item" ; type="text/plain"',
'<https://customlicense.org/licenses/by/4.0/> ; rel="license"',
'<https://creativecommons.org/licenses/by/4.0/legalcode> ; rel="license"',
'<https://schema.org/Photograph> ; rel="type"',
'<https://schema.org/AboutPage> ; rel="type"',
f'<{api_url}> ; rel="linkset" ; type="application/linkset+json"',
]

serialized = FAIRSignpostingProfileLvl1Serializer().serialize_object(
full_record_to_dict
)

assert expected == serialized.split(" , ")


def test_signposting_serializer_minimal(running_app, minimal_record_to_dict):
expected = {
"linkset": [
Expand All @@ -146,10 +181,6 @@ def test_signposting_serializer_minimal(running_app, minimal_record_to_dict):
"href": "https://127.0.0.1:5000/api/records/67890-fghij",
"type": "application/ld+json",
},
{
"href": "https://127.0.0.1:5000/api/records/67890-fghij",
"type": "application/linkset+json",
},
{
"href": "https://127.0.0.1:5000/api/records/67890-fghij",
"type": "application/marcxml+xml",
Expand Down Expand Up @@ -218,3 +249,37 @@ def test_signposting_serializer_minimal(running_app, minimal_record_to_dict):
serialized = FAIRSignpostingProfileLvl2Serializer().dump_obj(minimal_record_to_dict)

assert expected == serialized


def test_signposting_lvl1_serializer_minimal(running_app, minimal_record_to_dict):
api_url = "https://127.0.0.1:5000/api/records/67890-fghij"

expected = [
# No author since no associated PID
# No cite-as since no DOI
f'<{api_url}> ; rel="describedby" ; type="application/dcat+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/json"',
f'<{api_url}> ; rel="describedby" ; type="application/ld+json"',
f'<{api_url}> ; rel="describedby" ; type="application/marcxml+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.citationstyles.csl+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+xml"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.geo+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1+json"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.full+csv"',
f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.simple+csv"',
f'<{api_url}> ; rel="describedby" ; type="application/x-bibtex"',
f'<{api_url}> ; rel="describedby" ; type="application/x-dc+xml"',
f'<{api_url}> ; rel="describedby" ; type="text/x-bibliography"',
# No files
# No license
'<https://schema.org/Photograph> ; rel="type"',
'<https://schema.org/AboutPage> ; rel="type"',
f'<{api_url}> ; rel="linkset" ; type="application/linkset+json"',
]

serialized = FAIRSignpostingProfileLvl1Serializer().serialize_object(
minimal_record_to_dict
)

assert expected == serialized.split(" , ")
Loading