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

add create record function #149

Merged
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
11 changes: 10 additions & 1 deletion invenio_records_lom/services/components.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 Graz University of Technology.
# Copyright (C) 2021-2024 Graz University of Technology.
#
# invenio-records-lom 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 @@ -24,6 +24,7 @@
)
from invenio_records_resources.services.uow import TaskOp

from ..utils import LOMMetadata
from .tasks import register_or_update_pid


Expand Down Expand Up @@ -91,6 +92,14 @@ def new_version(
class LOMPIDsComponent(PIDsComponent):
"""LOM Sevice component for PIDs."""

def create(self, identity, data=None, record=None, errors=None):
"""This method is called on draft creation."""
super().create(identity, data, record, errors)

metadata = LOMMetadata(data["metadata"])
metadata.append_identifier(record.pid.pid_value, catalog="repo-pid")
record.metadata = metadata.json

# overwrite `publish`` to use the celery-task from this package
# this was copied from its parent class, except for its last line
def publish(self, identity, draft=None, record=None):
Expand Down
23 changes: 1 addition & 22 deletions invenio_records_lom/services/services.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 Graz University of Technology.
# Copyright (C) 2021-2024 Graz University of Technology.
#
# invenio-records-lom is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Record services configured for LOM-use."""

from invenio_rdm_records.services import RDMRecordService
from invenio_records_resources.services.uow import unit_of_work

from ..utils import LOMMetadata


# pylint: disable-next=abstract-method
class LOMRecordService(RDMRecordService):
"""RecordService configured for LOM-use."""

@unit_of_work()
def create(self, identity, data, uow=None, expand=False):
"""Create."""
draft_item = super().create(identity, data, uow=uow, expand=expand)

# add repo-pid to the record's identifiers
metadata = LOMMetadata(data["metadata"])
metadata.append_identifier(draft_item.id, catalog="repo-pid")
data["metadata"] = metadata.json

return super().update_draft(
identity=identity,
id_=draft_item.id,
data=data,
uow=uow,
expand=expand,
)
7 changes: 6 additions & 1 deletion invenio_records_lom/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

"""Utilities for creation of LOM-compliant metadata."""

from .metadata import LOMCourseMetadata, LOMMetadata
from .metadata import LOMCourseMetadata, LOMMetadata, LOMRecordData
from .statistics import build_record_unique_id
from .util import (
DotAccessWrapper,
LOMDuplicateRecordError,
check_about_duplicate,
create_record,
get_learningresourcetypedict,
get_oefosdict,
update_record,
)
from .vcard import make_lom_vcard

Expand All @@ -24,8 +26,11 @@
"get_oefosdict",
"LOMMetadata",
"LOMCourseMetadata",
"LOMRecordData",
"make_lom_vcard",
"build_record_unique_id",
"check_about_duplicate",
"create_record",
"update_record",
"LOMDuplicateRecordError",
)
45 changes: 43 additions & 2 deletions invenio_records_lom/utils/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,36 @@
)


class LOMRecordData(dict):
"""LOM record data."""

def __init__(
self,
resource_type=None,
pids=None,
metadata=None,
**kwargs: dict,
) -> None:
"""Construct."""
self.update(**kwargs)
self.resource_type = resource_type
self.pids = pids
self.metadata = (
metadata
if isinstance(metadata, LOMMetadata)
else LOMMetadata(metadata, overwritable=True)
)

@property
def json(self) -> dict:
"""Json."""
return {
"pids": self.pids,
"metadata": self.metadata.json,
"resource_type": self.resource_type,
}


class BaseLOMMetadata:
"""Base LOM Metadata."""

Expand Down Expand Up @@ -52,6 +82,10 @@ def deduped_append(self, parent_key: str, value: Any):
"""Append `value` to `self.record[key]` if not already appended."""
self.record.setdefault(parent_key, [])
parent = self.record[parent_key]

if not isinstance(parent, list):
parent = [parent]

if value not in parent:
parent.append(value)

Expand Down Expand Up @@ -194,7 +228,7 @@ def create(
def append_course(self, course: LOMCourseMetadata) -> None:
"""Append course."""
# pylint: disable-next=unsupported-membership-test
if "courses" not in self.record["metadata"]:
if "courses" not in self.record:
self.record["courses"] = []

courses = self.record["courses"]
Expand All @@ -205,6 +239,13 @@ def append_course(self, course: LOMCourseMetadata) -> None:
if course.record["course.version"] not in versions:
self.record["courses"].append(course.record.data)

def get_courses(self) -> list:
"""Get courses."""
try:
return self.record["courses"]
except KeyError:
return []

###############
#
# methods for manipulating LOM's `general` (1) category
Expand Down Expand Up @@ -523,7 +564,7 @@ def set_rights_url(self, url: str) -> None:
def get_rights(self, url_only=False) -> dict | str:
"""Get rights."""
if "rights" not in self.record:
return {}
return "" if url_only else {}

if url_only:
try:
Expand Down
86 changes: 83 additions & 3 deletions invenio_records_lom/utils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@

"""Utilities for creation of LOM-compliant metadata."""


import re
from collections.abc import MutableMapping
from csv import reader
from importlib import resources
from json import load
from pathlib import Path
from re import compile as re_compile
from re import sub
from time import sleep
from typing import Any, Iterator, Optional, Union

from flask_principal import Identity
from invenio_records_resources.services.base import Service
from invenio_search import RecordsSearch
from invenio_search.engine import dsl
from marshmallow.exceptions import ValidationError


class DotAccessWrapper(MutableMapping):
Expand Down Expand Up @@ -223,7 +228,7 @@ def standardize_url(url: str) -> str:
If `url`'s scheme is "http" or "https", make it "https".
Also ensure existence of a trailing "/".
"""
pattern = re.compile("^https?://(.*?)/?$")
pattern = re_compile("^https?://(.*?)/?$")
if m := pattern.match(url):
middle = m.group(1) # excludes initial "https://", excludes trailing "/"
return f"https://{middle}/"
Expand Down Expand Up @@ -265,3 +270,78 @@ def check_about_duplicate(identifier: str, catalog: str):
catalog=catalog,
id_=results[0]["id"],
)


def add_file_to_record(
lomid,
file_path,
file_service,
identity,
):
"""Add the file to the record."""
file_ = Path(file_path)
filename = sub(r"-([^-]*?)\.", r".", file_.name)
data = [{"key": filename}]

with file_.open(mode="rb") as file_pointer:
file_service.init_files(id_=lomid, identity=identity, data=data)
file_service.set_file_content(
id_=lomid, file_key=filename, identity=identity, stream=file_pointer
)
file_service.commit_file(id_=lomid, file_key=filename, identity=identity)


def create_record(
service: Service, # services.LOMRecordService
data: dict,
file_paths: list,
identity: Identity,
*,
do_publish: bool = True,
):
"""Create record."""
data["files"] = {"enabled": len(file_paths) > 0}
data["access"] = {
"access": {
"record": "public",
"files": "public",
},
}

draft = service.create(data=data, identity=identity)

try:
for file_path in file_paths:
add_file_to_record(
lomid=draft.id,
file_path=file_path,
file_service=service.draft_files,
identity=identity,
)

if do_publish:
# to prevent the race condition bug.
# see https://github.com/inveniosoftware/invenio-rdm-records/issues/809
sleep(0.5)

return service.publish(id_=draft.id, identity=identity)
except (FileNotFoundError, ValidationError) as error:
service.delete_draft(id_=draft.id, identity=identity)
raise error

return draft


def update_record(
pid: str,
service: Service, # services.LOMRecordService
data: dict,
identity: Identity,
*,
do_publish: bool = True,
):
"""Update record."""
service.update_draft(id_=pid, data=data, identity=identity)

if do_publish:
service.publish(id_=pid, identity=identity)
6 changes: 2 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2022 Graz University of Technology.
# Copyright (C) 2020-2024 Graz University of Technology.
#
# invenio-records-lom 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,9 +20,7 @@ author_email = [email protected]
platforms = any
url = https://github.com/tu-graz-library/invenio-records-lom
classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Development Status :: 5 - Production/Stable
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Expand Down
2 changes: 2 additions & 0 deletions tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def test_create_draft(service, db, identity, access): # pylint: disable=too-man

# TODO: check the necessity of this
del data["metadata"]["type"]
del json["metadata"]["general"]["identifier"]
assert json["metadata"] == data["metadata"]
assert "access" in json
assert json["access"]["files"] == access["files"]
Expand Down Expand Up @@ -165,6 +166,7 @@ def test_publish(service, db, identity, access): # pylint: disable=too-many-loc
json_pid = general["identifier"][0]["entry"]["langstring"]["#text"]
assert json_pid == record_pid.pid_value

del json["metadata"]["general"]["identifier"]
assert json["metadata"] == data["metadata"]
assert "access" in json
assert json["access"]["files"] == access["files"]
Expand Down