Skip to content

Commit

Permalink
services: Add support for nested links (#564)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Ioannidis <a.ioannidis@cern.ch>
  • Loading branch information
yashlamba and slint committed Apr 23, 2024
1 parent ce01601 commit f689831
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 4 deletions.
1 change: 1 addition & 0 deletions invenio_records_resources/factories/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def create_service_class(self):
"self": RecordLink("{+api}" + route + "/{id}"),
},
"links_search": pagination_links("{+api}" + route + "{?args*}"),
"nested_links_item": None,
}
if self.service_components:
config_cls_attributes.update({"components": self.service_components})
Expand Down
11 changes: 9 additions & 2 deletions invenio_records_resources/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@

"""High-level API for wokring with records, files, pids and search."""


from .base import ConditionalLink, Link, LinksTemplate, Service, ServiceConfig
from .base import (
ConditionalLink,
Link,
LinksTemplate,
NestedLinks,
Service,
ServiceConfig,
)
from .files import FileLink, FileService, FileServiceConfig
from .records import (
RecordIndexerMixin,
Expand Down Expand Up @@ -37,4 +43,5 @@
"ServiceConfig",
"ServiceSchemaWrapper",
"RecordIndexerMixin",
"NestedLinks",
)
3 changes: 2 additions & 1 deletion invenio_records_resources/services/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""Base Service API."""

from .config import ServiceConfig
from .links import ConditionalLink, Link, LinksTemplate
from .links import ConditionalLink, Link, LinksTemplate, NestedLinks
from .results import ServiceItemResult, ServiceListResult
from .service import Service

Expand All @@ -22,4 +22,5 @@
"ServiceConfig",
"ServiceItemResult",
"ServiceListResult",
"NestedLinks",
)
50 changes: 50 additions & 0 deletions invenio_records_resources/services/base/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

"""Utility for rendering URI template links."""

import operator
from copy import deepcopy

from flask import current_app
from invenio_records.dictutils import dict_lookup
from uritemplate import URITemplate
from werkzeug.datastructures import MultiDict

Expand Down Expand Up @@ -145,3 +147,51 @@ def expand(self, obj, ctx):
return self._if_link.expand(obj, ctx)
else:
return self._else_link.expand(obj, ctx)


class NestedLinks:
"""Base class for generating nested links."""

def __init__(
self,
links,
key=None,
load_key=None,
dump_key=None,
context_func=None,
):
"""Initialize NestedLinkGenerator."""
self.links = links
self.load_key = load_key or key
self.dump_key = dump_key or key
assert self.load_key and self.dump_key
self.context_func = context_func

def context(self, identity, record, key, value):
"""Get the context for the links."""
if not self.context_func:
return {}
return self.context_func(identity, record, key, value)

def expand(self, identity, record, data):
"""Update data with links in each object inside the dictionary."""
try:
record_data = operator.attrgetter(self.load_key)(record)
except AttributeError:
return
try:
output_data = dict_lookup(data, self.dump_key)
except KeyError:
return

if isinstance(record_data, (tuple, list)):
items_iter = enumerate(record_data)
elif isinstance(record_data, dict):
items_iter = record_data.items()
else:
return

for key, value in items_iter:
context = self.context(identity, record, key, value)
links = LinksTemplate(self.links, context=context).expand(identity, value)
output_data[key]["links"] = links
13 changes: 12 additions & 1 deletion invenio_records_resources/services/records/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
# details.

"""Service results."""

from abc import ABC, abstractmethod

from invenio_access.permissions import system_user_id
from invenio_records.dictutils import dict_lookup, dict_merge, dict_set

from ...pagination import Pagination
from ..base import ServiceItemResult, ServiceListResult
from .schema import BaseGhostSchema


class RecordItem(ServiceItemResult):
Expand All @@ -31,6 +31,7 @@ def __init__(
schema=None,
expandable_fields=None,
expand=False,
nested_links_item=None,
):
"""Constructor."""
self._errors = errors
Expand All @@ -41,6 +42,7 @@ def __init__(
self._schema = schema or service.schema
self._fields_resolver = FieldsResolver(expandable_fields)
self._expand = expand
self._nested_links_item = nested_links_item
self._data = None

@property
Expand Down Expand Up @@ -78,6 +80,10 @@ def data(self):
if self._links_tpl:
self._data["links"] = self.links

if self._nested_links_item:
for link in self._nested_links_item:
link.expand(self._identity, self._record, self._data)

if self._expand and self._fields_resolver:
self._fields_resolver.resolve(self._identity, [self._data])
fields = self._fields_resolver.expand(self._identity, self._data)
Expand Down Expand Up @@ -135,6 +141,7 @@ def __init__(
params=None,
links_tpl=None,
links_item_tpl=None,
nested_links_item=None,
schema=None,
expandable_fields=None,
expand=False,
Expand All @@ -153,6 +160,7 @@ def __init__(
self._params = params
self._links_tpl = links_tpl
self._links_item_tpl = links_item_tpl
self._nested_links_item = nested_links_item
self._fields_resolver = FieldsResolver(expandable_fields)
self._expand = expand

Expand Down Expand Up @@ -201,6 +209,9 @@ def hits(self):
projection["links"] = self._links_item_tpl.expand(
self._identity, record
)
if self._nested_links_item:
for link in self._nested_links_item:
link.expand(self._identity, record, projection)

yield projection

Expand Down
3 changes: 3 additions & 0 deletions invenio_records_resources/services/records/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ def _create(
identity,
record,
links_tpl=self.links_item_tpl,
nested_links_item=getattr(self.config, "nested_links_item", None),
errors=errors,
expandable_fields=self.expandable_fields,
expand=expand,
Expand All @@ -387,6 +388,7 @@ def read(self, identity, id_, expand=False, action="read"):
record,
links_tpl=self.links_item_tpl,
expandable_fields=self.expandable_fields,
nested_links_item=getattr(self.config, "nested_links_item", None),
expand=expand,
)

Expand Down Expand Up @@ -490,6 +492,7 @@ def update(self, identity, id_, data, revision_id=None, uow=None, expand=False):
identity,
record,
links_tpl=self.links_item_tpl,
nested_links_item=getattr(self.config, "nested_links_item", None),
expandable_fields=self.expandable_fields,
expand=expand,
)
Expand Down
1 change: 1 addition & 0 deletions tests/mock_module/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class ServiceConfig(RecordServiceConfig):
}

links_search = pagination_links("{+api}/mocks{?args*}")
nested_links_item = None


class ServiceWithFilesConfig(ServiceConfig):
Expand Down

0 comments on commit f689831

Please sign in to comment.