Skip to content

Commit 382e2c3

Browse files
author
notactuallyfinn
committed
fixed bug and adjusted tests
1 parent ed0916b commit 382e2c3

7 files changed

Lines changed: 129 additions & 90 deletions

File tree

src/hermes/commands/deposit/base.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __call__(self, command: HermesCommand) -> None:
3838
deposit = self.map_metadata()
3939
self.ctx.prepare_step("deposit")
4040
with self.ctx[command.settings.target] as cache:
41-
cache["deposit"] = deposit.compact()
41+
cache["deposit"] = deposit
4242
self.ctx.finalize_step("deposit")
4343

4444
if self.is_initial_publication():
@@ -48,10 +48,8 @@ def __call__(self, command: HermesCommand) -> None:
4848

4949
deposit = self.update_metadata()
5050
self.ctx.prepare_step("deposit")
51-
with self.ctx[command.settings.target] as cache:
52-
cache["codemeta"] = deposit.compact()
53-
cache["expanded"] = deposit.ld_value
54-
cache["context"] = {"@context": deposit.full_context}
51+
with self.ctx["deposit"] as cache:
52+
cache["result"] = deposit
5553
self.ctx.finalize_step("deposit")
5654
self.delete_artifacts()
5755
self.upload_artifacts()
@@ -67,7 +65,7 @@ def prepare(self) -> None:
6765
pass
6866

6967
@abc.abstractmethod
70-
def map_metadata(self) -> SoftwareMetadata:
68+
def map_metadata(self) -> dict:
7169
"""Map the given metadata to the target schema of the deposition platform and return it.
7270
7371
When mapping metadata, make sure to add traces to the HERMES software, e.g. via
@@ -97,9 +95,10 @@ def create_new_version(self) -> None:
9795
"""Create a new version of an existing publication on the target platform."""
9896
pass
9997

100-
def update_metadata(self) -> SoftwareMetadata:
98+
@abc.abstractmethod
99+
def update_metadata(self) -> dict:
101100
"""Update the metadata of the newly created version and return it even if it hasn't changed."""
102-
return self.metadata
101+
pass
103102

104103
def delete_artifacts(self) -> None:
105104
"""Delete any superfluous artifacts taken from the previous version of the publication."""

src/hermes/commands/deposit/file.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pydantic import BaseModel
1212

1313
from hermes.commands.deposit.base import BaseDepositPlugin
14-
from hermes.model import SoftwareMetadata
14+
1515

1616
class FileDepositSettings(BaseModel):
1717
filename: str = 'codemeta.json'
@@ -20,8 +20,11 @@ class FileDepositSettings(BaseModel):
2020
class FileDepositPlugin(BaseDepositPlugin):
2121
settings_class = FileDepositSettings
2222

23-
def map_metadata(self) -> SoftwareMetadata:
24-
return self.metadata
23+
def map_metadata(self) -> dict:
24+
return self.metadata.compact()
25+
26+
def update_metadata(self) -> dict:
27+
return self.metadata.compact()
2528

2629
def publish(self) -> None:
2730
file_config = self.command.settings.file

src/hermes/commands/deposit/invenio.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from hermes.commands.deposit.base import BaseDepositPlugin
2020
from hermes.commands.deposit.error import DepositionUnauthorizedError
2121
from hermes.error import MisconfigurationError
22-
from hermes.model import SoftwareMetadata
2322
from hermes.model.error import HermesValidationError
2423
from hermes.utils import hermes_doi, hermes_user_agent
2524

@@ -320,7 +319,12 @@ def prepare(self) -> None:
320319
record_id=rec_id, doi=doi, codemeta_identifier=codemeta_identifier
321320
)
322321

323-
version = self.metadata["version"]
322+
if len(self.metadata.get("version", [])) > 1:
323+
raise HermesValidationError("Too many licenses for invenio deposit.")
324+
if len(self.metadata.get("version", [])) == 1:
325+
version = self.metadata["version"][0]
326+
else:
327+
version = None
324328
if rec_meta and (version == rec_meta.get("version")):
325329
raise ValueError(f"Version {version} already deposited.")
326330

@@ -336,10 +340,10 @@ def prepare(self) -> None:
336340

337341
self.invenio_ctx = deposition_data
338342

339-
def map_metadata(self) -> SoftwareMetadata:
343+
def map_metadata(self) -> dict:
340344
"""Map the harvested metadata onto the Invenio schema and return it."""
341345
self.invenio_ctx["depositionMetadata"] = self._codemeta_to_invenio_deposition()
342-
return SoftwareMetadata(self.invenio_ctx["depositionMetadata"])
346+
return self.invenio_ctx["depositionMetadata"]
343347

344348
def is_initial_publication(self) -> bool:
345349
latest_record_id = self.invenio_ctx.get("latestRecord", {}).get("id")
@@ -398,7 +402,7 @@ def related_identifiers(self):
398402
},
399403
]
400404

401-
def update_metadata(self) -> SoftwareMetadata:
405+
def update_metadata(self) -> dict:
402406
"""Update the metadata of a draft and return it."""
403407

404408
draft_url = self.links["latest_draft"]
@@ -418,7 +422,7 @@ def update_metadata(self) -> SoftwareMetadata:
418422
self.links.update(deposit["links"])
419423

420424
_log.debug("Created new version deposit: %s", self.links["html"])
421-
return SoftwareMetadata(deposit.get("metadata", {}))
425+
return deposit
422426

423427
def delete_artifacts(self) -> None:
424428
"""Delete existing file artifacts.
@@ -508,21 +512,25 @@ def _codemeta_to_invenio_deposition(self) -> dict:
508512
access_conditions = self.invenio_ctx["access_conditions"]
509513

510514
creators = []
511-
for author in metadata["author"]:
515+
for author in metadata.get("author", []):
512516
creator = {}
513-
if len(affils := [name for affil in author["affiliation"] for name in affil["legalname"]]) != 0:
517+
if len(
518+
affils := [
519+
name for affil in author.get("affiliation", []) for name in affil.get("legalname", [])
520+
]
521+
) != 0:
514522
creator["affiliation"] = affils
515-
if len(author["familyName"]) > 1:
516-
raise HermesValidationError(f"Author has too many family names: {author.to_python()}")
517-
if len(author["familyName"]) == 1:
518-
given_names_str = " ".join(author["givenName"])
523+
524+
if len(author.get("familyName", [])) > 1:
525+
raise HermesValidationError(f"Author has too many family names: {author}")
526+
if len(author.get("familyName", [])) == 1:
527+
given_names_str = " ".join(author.get("givenName", []))
519528
name = f"{author["familyName"][0]}, {given_names_str}"
520-
elif len(author["name"]) != 1:
521-
raise HermesValidationError(f"Author has too many names: {author.to_python()}")
529+
elif len(author.get("name", [])) != 1:
530+
raise HermesValidationError(f"Author has too many or no names: {author}")
522531
else:
523532
name = author["name"][0]
524-
if len(name) != 0:
525-
creator["name"] = name
533+
creator["name"] = name
526534
if (id := author.get("@id", None)) is not None:
527535
creator["orcid"] = id.replace("https://orcid.org/", "")
528536
if creator:
@@ -545,6 +553,7 @@ def _codemeta_to_invenio_deposition(self) -> dict:
545553
for author in metadata["author"]
546554
]"""
547555

556+
# TODO: reimplement with new api
548557
# This is not used at the moment. See comment below in `deposition_metadata` dict.
549558
contributors = [ # noqa: F841
550559
# TODO: Distinguish between @type "Person" and others
@@ -566,27 +575,33 @@ def _codemeta_to_invenio_deposition(self) -> dict:
566575
for contributor in metadata.get("contributor", []) if contributor.get("name") != "GitHub"
567576
]
568577

569-
if len(metadata["name"]) != 1:
578+
if len(metadata.get("name", [])) != 1:
570579
_log.error("More than one or zero names for the Software are given.")
571580
raise HermesValidationError("More than one or zerno names for the Software.")
572581
name = metadata["name"][0]
573582

574-
if len(metadata["schema:description"]) > 1:
583+
if len(metadata.get("schema:description", [])) > 1:
575584
_log.error("More than one descriptions of the Software are given.")
576585
raise HermesValidationError("More than one descriptions of the Software are given.")
577-
if len(metadata["schema:description"]) == 1:
586+
if len(metadata.get("schema:description", [])) == 1:
578587
description = metadata["schema:description"][0]
579588
else:
580589
description = None
581590

582-
if len(metadata["schema:version"]) > 1:
591+
if len(metadata.get("schema:version", [])) > 1:
583592
_log.error("More than one version of the Software are given.")
584593
raise HermesValidationError("More than one version of the Software are given.")
585-
if len(metadata["schema:version"]) == 1:
594+
if len(metadata.get("schema:version", [])) == 1:
586595
version = metadata["schema:version"][0]
587596
else:
588597
version = None
589598

599+
keywords = metadata.get("schema:keywords", [])
600+
if len(keywords) == 0:
601+
keywords = None
602+
else:
603+
keywords = keywords.to_python()
604+
590605
# TODO: Use the fields currently set to `None`.
591606
# Some more fields are available but they most likely don't relate to software
592607
# publications targeted by hermes.
@@ -602,9 +617,6 @@ def _codemeta_to_invenio_deposition(self) -> dict:
602617
"publication_date": date.today().isoformat(),
603618
"title": name,
604619
"creators": creators,
605-
# TODO: Use a real description here. Possible sources could be
606-
# `tool.poetry.description` from pyproject.toml or `abstract` from
607-
# CITATION.cff. This should then be stored in codemeta description field.
608620
"description": description,
609621
"access_right": access_right,
610622
"license": license,
@@ -618,8 +630,8 @@ def _codemeta_to_invenio_deposition(self) -> dict:
618630
# them.
619631
# TODO: Use the DOI we get back from this.
620632
"prereserve_doi": True,
621-
# TODO: A good source for this could be `tool.poetry.keywords` in pyproject.toml.
622-
"keywords": None,
633+
"keywords": keywords,
634+
# TODO: Is there a good codemeta/ schema field?
623635
"notes": None,
624636
"related_identifiers": self.related_identifiers(),
625637
# TODO: Use `contributors`. In the case of the hermes workflow itself, the
@@ -641,6 +653,10 @@ def _get_license_identifier(self) -> Union[str, None]:
641653
642654
If no license is configured, ``None`` will be returned.
643655
"""
656+
if "license" not in self.metadata:
657+
raise HermesValidationError("No license is given.")
658+
if len(self.metadata["license"]) > 1:
659+
raise HermesValidationError("Too many licenses for invenio deposit.")
644660
license_url = self.metadata["license"][0]
645661
return self.resolver.resolve_license_id(license_url)
646662

src/hermes/model/types/ld_dict.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,7 @@ def __init__(self, data, *, parent=None, key=None, index=None, context=None):
2222

2323
def __getitem__(self, key):
2424
full_iri = self.ld_proc.expand_iri(self.active_ctx, key)
25-
if full_iri == "@id":
26-
return self._to_python(full_iri, self.data_dict[full_iri])
27-
try:
28-
ld_value = self.data_dict[full_iri]
29-
except KeyError:
30-
self[key] = []
31-
ld_value = self.data_dict[full_iri]
32-
return self._to_python(full_iri, ld_value)
25+
return self._to_python(full_iri, self.data_dict[full_iri])
3326

3427
def __setitem__(self, key, value):
3528
ld_value = self._to_expanded_json({key: value})
@@ -41,12 +34,7 @@ def __delitem__(self, key):
4134

4235
def __contains__(self, key):
4336
full_iri = self.ld_proc.expand_iri(self.active_ctx, key)
44-
if full_iri == "@id":
45-
return "@id" in self.data_dict
46-
try:
47-
return len(self[full_iri]) != 0
48-
except KeyError:
49-
return False
37+
return full_iri in self.data_dict
5038

5139
def __eq__(self, other):
5240
if not isinstance(other, (dict, ld_dict)):
@@ -89,6 +77,15 @@ def get(self, key, default=_NO_DEFAULT):
8977
return default
9078
return self[key]
9179

80+
def setdefault(self, key, default):
81+
if key not in self:
82+
self[key] = default
83+
return self[key]
84+
85+
def emplace(self, key):
86+
if key not in self:
87+
self[key] = []
88+
9289
def update(self, other):
9390
for key, value in other.items():
9491
self[key] = value
@@ -136,7 +133,7 @@ def from_dict(cls, value, *, parent=None, key=None, context=None, ld_type=None):
136133
full_context = parent.full_context + merged_contexts
137134

138135
ld_value = cls.ld_proc.expand(ld_data, {"expandContext": full_context, "documentLoader": bundled_loader})
139-
ld_value = cls(ld_value, parent=parent, key=key, context=merged_contexts)
136+
ld_value = ld_dict(ld_value, parent=parent, key=key, context=merged_contexts)
140137

141138
return ld_value
142139

test/hermes_test/model/test_api.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ def test_init_nested_object():
5353

5454
def test_append():
5555
data = SoftwareMetadata()
56+
data.emplace("schema:name")
5657
data["schema:name"].append("a")
5758
assert type(data["schema:name"]) is ld_list
5859
assert data["schema:name"][0] == "a" and data["schema:name"].item_list == [{"@value": "a"}]
5960
data["schema:name"].append("b")
6061
assert type(data["schema:name"]) is ld_list and data["schema:name"].item_list == [{"@value": "a"}, {"@value": "b"}]
62+
data.emplace("schema:name")
6163
data["schema:name"].append("c")
6264
assert data["schema:name"].item_list == [{"@value": "a"}, {"@value": "b"}, {"@value": "c"}]
6365

6466
data = SoftwareMetadata()
65-
data["schema:Person"].append({"schema:name": "foo"})
67+
data.setdefault("schema:Person", []).append({"schema:name": "foo"})
6668
assert type(data["schema:Person"]) is ld_list and type(data["schema:Person"][0]) is ld_dict
6769
assert data["schema:Person"][0].data_dict == {"http://schema.org/name": [{"@value": "foo"}]}
6870
data["schema:Person"].append({"schema:name": "foo"})
@@ -94,7 +96,7 @@ def test_usage():
9496
data["author"][0]["email"].append("foo@baz.com")
9597
assert len(data["author"]) == 2
9698
assert len(data["author"][0]["email"]) == 2
97-
assert len(data["author"][1]["email"]) == 0
99+
assert len(data["author"][1].get("email", [])) == 0
98100
harvest = {
99101
"authors": [
100102
{"name": "Foo", "affiliation": ["Uni A", "Lab B"], "kw": ["a", "b", "c"]},
@@ -103,17 +105,19 @@ def test_usage():
103105
]
104106
}
105107
for author in harvest["authors"]:
106-
for exist_author in data["author"]:
107-
if author["name"] == exist_author["name"][0]:
108+
for exist_author in data.get("author", []):
109+
if author["name"] in exist_author.get("name", []):
108110
exist_author["affiliation"] = author["affiliation"]
109111
if "email" in author:
112+
exist_author.emplace("email")
110113
exist_author["email"].append(author["email"])
111114
if "kw" in author:
115+
exist_author.emplace("schema:knowsAbout")
112116
exist_author["schema:knowsAbout"].extend(author["kw"])
113117
break
114118
else:
115-
data["author"].append(author)
116-
assert len(data["author"]) == 3
119+
data.setdefault("author", []).append(author)
120+
assert len(data.get("author", [])) == 3
117121
foo, bar, baz = data["author"]
118122
assert foo["name"][0] == "Foo"
119123
assert foo["affiliation"].to_python() == ["Uni A", "Lab B"]
@@ -124,8 +128,8 @@ def test_usage():
124128
assert bar["email"].to_python() == ["bar@c.edu"]
125129
assert baz["name"][0] == "Baz"
126130
assert baz["affiliation"].to_python() == ["Lab E"]
127-
assert len(baz["schema:knowsAbout"]) == 0
128-
assert len(baz["email"]) == 0
131+
assert len(baz.get("schema:knowsAbout", [])) == 0
132+
assert len(baz.get("email", [])) == 0
129133
for author in data["author"]:
130134
assert "name" in author
131135
if "Baz" not in author["name"]:

0 commit comments

Comments
 (0)