-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
866 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
invenio_rdm_records/resources/serializers/signposting/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (C) 2023 Northwestern University. | ||
# | ||
# 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 MarshmallowSerializer | ||
from flask_resources.serializers import JSONSerializer | ||
|
||
from .schema import FAIRSignpostingProfileLvl2Schema | ||
|
||
|
||
class FAIRSignpostingProfileLvl2Serializer(MarshmallowSerializer): | ||
"""FAIR Signposting Profile level 2 serializer.""" | ||
|
||
def __init__(self): | ||
"""Initialise Serializer.""" | ||
super().__init__( | ||
format_serializer_cls=JSONSerializer, | ||
object_schema_cls=FAIRSignpostingProfileLvl2Schema, | ||
) |
212 changes: 212 additions & 0 deletions
212
invenio_rdm_records/resources/serializers/signposting/schema.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (C) 2023 Northwestern University. | ||
# | ||
# 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 schemas.""" | ||
|
||
import idutils | ||
from marshmallow import Schema, fields, missing | ||
|
||
from ...urls import download_url_for | ||
from ..utils import get_vocabulary_props | ||
|
||
|
||
class LandingPageSchema(Schema): | ||
"""Schema for serialization of typed links pertaining to the landing page. | ||
Serialization input 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). | ||
For now, the first linkable identifier is taken. | ||
""" | ||
|
||
def pick_linkable_id(identifiers): | ||
for id_dict in identifiers: | ||
url = idutils.to_url( | ||
id_dict["identifier"], id_dict["scheme"], url_scheme="https" | ||
) | ||
if url: | ||
return url | ||
else: | ||
continue | ||
return None | ||
|
||
metadata = obj["metadata"] | ||
result = [ | ||
{"href": pick_linkable_id(c["person_or_org"].get("identifiers", []))} | ||
for c in metadata.get("creators", []) | ||
] | ||
result = [r for r in result if r["href"]] | ||
return result or missing | ||
|
||
def serialize_cite_as(self, obj, **kwargs): | ||
"""Serialize cite-as.""" | ||
identifier = obj.get("pids", {}).get("doi", {}).get("identifier") | ||
if not identifier: | ||
return missing | ||
|
||
url = idutils.to_url(identifier, "doi", "https") | ||
|
||
return [{"href": url}] if url else missing | ||
|
||
def serialize_describedby(self, obj, **kwargs): | ||
"""Serialize describedby.""" | ||
# Has to be placed here to prevent circular dependency. | ||
from invenio_rdm_records.resources.config import record_serializers | ||
|
||
result = [ | ||
{"href": obj["links"]["self"], "type": mimetype} | ||
for mimetype in sorted(record_serializers) | ||
] | ||
|
||
return result or missing | ||
|
||
def serialize_item(self, obj, **kwargs): | ||
"""Serialize item.""" | ||
file_entries = obj.get("files", {}).get("entries", []) | ||
|
||
result = [ | ||
{ | ||
"href": download_url_for(pid_value=obj["id"], filename=entry["key"]), | ||
"type": entry["mimetype"], | ||
} | ||
for entry in file_entries | ||
] | ||
|
||
return result or missing | ||
|
||
def serialize_license(self, obj, **kwargs): | ||
"""Serialize license. | ||
Note that we provide an entry for each license (rather than just 1). | ||
""" | ||
rights = obj["metadata"].get("rights", []) | ||
|
||
result = [{"href": right["link"]} for right in rights if right.get("link")] | ||
|
||
return result or missing | ||
|
||
def serialize_type(self, obj, **kwargs): | ||
"""Serialize type.""" | ||
resource_type = obj["metadata"]["resource_type"] | ||
|
||
props = get_vocabulary_props( | ||
"resourcetypes", | ||
[ | ||
"props.schema.org", | ||
], | ||
resource_type["id"], | ||
) | ||
url_schema_org = props.get("schema.org") | ||
|
||
result = [] | ||
if url_schema_org: | ||
result.append({"href": url_schema_org}) | ||
# always provide About Page | ||
result.append({"href": "https://schema.org/AboutPage"}) | ||
|
||
return result | ||
|
||
|
||
class ContentResourceSchema(Schema): | ||
"""Schema for serialization of typed links pertaining to the content resource. | ||
Serialization input is a file entry dict projection. | ||
Passing a context={"record_dict"} to the constructor is required. | ||
""" | ||
|
||
anchor = fields.Method(serialize="serialize_anchor") | ||
collection = fields.Method(serialize="serialize_collection") | ||
|
||
def serialize_anchor(self, obj, **kwargs): | ||
"""Serialize to download url.""" | ||
pid_value = self.context["record_dict"]["id"] | ||
return download_url_for(pid_value=pid_value, filename=obj["key"]) | ||
|
||
def serialize_collection(self, obj, **kwargs): | ||
"""Serialize to record landing page url.""" | ||
return [ | ||
{ | ||
"href": self.context["record_dict"]["links"]["self_html"], | ||
"type": "text/html", | ||
} | ||
] | ||
|
||
|
||
class MetadataResourceSchema(Schema): | ||
"""Schema for serialization of typed links pertaining to the metadata resource. | ||
Serialization input is a mimetype. | ||
Passing a context={"record_dict"} to the constructor is required. | ||
""" | ||
|
||
anchor = fields.Method(serialize="serialize_anchor") | ||
describes = fields.Method(serialize="serialize_describes") | ||
type = fields.Method(serialize="serialize_type") | ||
|
||
def serialize_anchor(self, obj, **kwargs): | ||
"""Serialize to API url.""" | ||
return self.context["record_dict"]["links"]["self"] | ||
|
||
def serialize_describes(self, obj, **kwargs): | ||
"""Serialize to record landing page url.""" | ||
return [ | ||
{ | ||
"href": self.context["record_dict"]["links"]["self_html"], | ||
"type": "text/html", | ||
} | ||
] | ||
|
||
def serialize_type(self, obj, **kwargs): | ||
"""Serialize to mimetype i.e. obj.""" | ||
return obj | ||
|
||
|
||
class FAIRSignpostingProfileLvl2Schema(Schema): | ||
"""FAIR Signposting Profile Lvl 2 Schema. | ||
See https://signposting.org/FAIR/ | ||
""" | ||
|
||
linkset = fields.Method(serialize="serialize_linkset") | ||
|
||
def serialize_linkset(self, obj, **kwargs): | ||
"""Serialize linkset.""" | ||
# Has to be placed here to prevent circular dependency. | ||
from invenio_rdm_records.resources.config import record_serializers | ||
|
||
result = [LandingPageSchema().dump(obj)] | ||
|
||
content_resource_schema = ContentResourceSchema(context={"record_dict": obj}) | ||
result += [ | ||
content_resource_schema.dump(entry) | ||
for entry in obj.get("files", {}).get("entries", []) | ||
] | ||
|
||
metadata_resource_schema = MetadataResourceSchema(context={"record_dict": obj}) | ||
result += [ | ||
metadata_resource_schema.dump(mimetype) | ||
for mimetype in sorted(record_serializers) | ||
] | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (C) 2023 Northwestern University. | ||
# | ||
# 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. | ||
|
||
"""Convenient URL generation. | ||
InvenioRDM poses challenges to url generation that Flask's url_for cannot handle out | ||
of the gate. | ||
- InvenioRDM is actually 2 applications mounted on different url_prefixes: | ||
`url_for` in the API application isn't aware of the UI application endpoints | ||
- The routes are configurable via the `APP_RDM_ROUTES` configuration. | ||
- But `APP_RDM_ROUTES` is not used for `RDMRecordServiceConfig.links_item` leading to | ||
conflicts and inconsistencies. | ||
- The endpoint names are relatively hidden / spread out and APP_RDM_ROUTES does have | ||
its own endpoint naming convention. | ||
- All other url generation use cases need to interact with this: generating urls outside | ||
of a request context, generating canonical urls... | ||
This module contains minimal methods to generate URLs correctly without much | ||
engineering. Over time, it can be made more abstract, complex and powerful and even | ||
extracted into its own package to solve url generation across InvenioRDM once and for | ||
all. | ||
""" | ||
|
||
from flask import current_app | ||
|
||
|
||
def record_url_for(_app="ui", pid_value=""): | ||
"""Return url for record route.""" | ||
assert _app in ["ui", "api"] | ||
|
||
site_app = _app.upper() | ||
url_prefix = current_app.config.get(f"SITE_{site_app}_URL", "") | ||
|
||
# We use [] so that this fails and brings to attention the configuration | ||
# problem if APP_RDM_ROUTES.record_detail is missing | ||
url_path = current_app.config["APP_RDM_ROUTES"]["record_detail"].replace( | ||
"<pid_value>", pid_value | ||
) | ||
|
||
return "/".join(p.strip("/") for p in [url_prefix, url_path]) | ||
|
||
|
||
def download_url_for(pid_value="", filename=""): | ||
"""Return url for download route.""" | ||
url_prefix = current_app.config.get(f"SITE_UI_URL", "") | ||
|
||
# We use [] so that this fails and brings to attention the configuration | ||
# problem if APP_RDM_ROUTES.record_file_download is missing | ||
url_path = ( | ||
current_app.config["APP_RDM_ROUTES"]["record_file_download"] | ||
.replace("<pid_value>", pid_value) | ||
.replace("<path:filename>", filename) | ||
) | ||
|
||
return "/".join(p.strip("/") for p in [url_prefix, url_path]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.