Skip to content

Commit

Permalink
Merge branch 'main' into wip/DEV-4111-kunsthalle
Browse files Browse the repository at this point in the history
  • Loading branch information
jnussbaum authored Oct 11, 2024
2 parents 31bf2be + 9f675f9 commit 8c3de40
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 14 deletions.
5 changes: 5 additions & 0 deletions dsp_permissions_scripts/models/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ class EmptyScopeError(Exception):
@dataclass
class InvalidGroupError(Exception):
message: str


@dataclass
class InvalidIRIError(Exception):
message: str
8 changes: 4 additions & 4 deletions dsp_permissions_scripts/oap/oap_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _enrich_with_value_oaps(
complete_oaps = copy.deepcopy(res_only_oaps)
for oap in complete_oaps:
full_resource = dsp_client.get(f"/v2/resources/{quote_plus(oap.resource_oap.resource_iri)}")
oap.value_oaps = _get_value_oaps(full_resource, restrict_to_props)
oap.value_oaps = get_value_oaps(full_resource, restrict_to_props)
logger.info(f"Enriched {len(complete_oaps)} OAPs of knora-base resources with their value OAPs.")
return complete_oaps

Expand Down Expand Up @@ -194,14 +194,14 @@ def _get_oap_of_one_resource(r: dict[str, Any], oap_config: OapRetrieveConfig) -
if oap_config.retrieve_values == "none":
value_oaps = []
elif oap_config.retrieve_values == "all":
value_oaps = _get_value_oaps(r)
value_oaps = get_value_oaps(r)
else:
value_oaps = _get_value_oaps(r, oap_config.specified_props)
value_oaps = get_value_oaps(r, oap_config.specified_props)

return Oap(resource_oap=resource_oap, value_oaps=value_oaps)


def _get_value_oaps(resource: dict[str, Any], restrict_to_props: list[str] | None = None) -> list[ValueOap]:
def get_value_oaps(resource: dict[str, Any], restrict_to_props: list[str] | None = None) -> list[ValueOap]:
res = []
for k, v in resource.items():
if k in IGNORE_KEYS:
Expand Down
8 changes: 4 additions & 4 deletions dsp_permissions_scripts/oap/oap_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
logger = get_logger(__name__)


def _update_permissions_for_value(
def update_permissions_for_value(
value: ValueOap,
resource_type: str,
context: dict[str, str],
Expand All @@ -44,7 +44,7 @@ def _update_permissions_for_value(
raise err from None


def _update_permissions_for_resource( # noqa: PLR0913
def update_permissions_for_resource( # noqa: PLR0913
resource_iri: str,
lmd: str | None,
resource_type: str,
Expand Down Expand Up @@ -86,7 +86,7 @@ def _update_batch(batch: tuple[ModifiedOap, ...], dsp_client: DspClient) -> list
continue
if oap.resource_oap:
try:
_update_permissions_for_resource(
update_permissions_for_resource(
resource_iri=oap.resource_oap.resource_iri,
lmd=resource.get("knora-api:lastModificationDate"),
resource_type=resource["@type"],
Expand All @@ -99,7 +99,7 @@ def _update_batch(batch: tuple[ModifiedOap, ...], dsp_client: DspClient) -> list
failed_iris.append(oap.resource_oap.resource_iri)
for val_oap in oap.value_oaps:
try:
_update_permissions_for_value(
update_permissions_for_value(
value=val_oap,
resource_type=resource["@type"],
context=resource["@context"] | {"knora-admin": KNORA_ADMIN_ONTO_NAMESPACE},
Expand Down
119 changes: 119 additions & 0 deletions dsp_permissions_scripts/oap/update_iris.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from __future__ import annotations

import re
from abc import ABC
from abc import abstractmethod
from dataclasses import dataclass
from dataclasses import field
from pathlib import Path
from typing import Any
from urllib.parse import quote_plus

from dsp_permissions_scripts.models.errors import ApiError
from dsp_permissions_scripts.models.errors import InvalidIRIError
from dsp_permissions_scripts.models.group import KNORA_ADMIN_ONTO_NAMESPACE
from dsp_permissions_scripts.models.scope import PermissionScope
from dsp_permissions_scripts.oap.oap_get import get_value_oaps
from dsp_permissions_scripts.oap.oap_set import update_permissions_for_resource
from dsp_permissions_scripts.oap.oap_set import update_permissions_for_value
from dsp_permissions_scripts.utils.dsp_client import DspClient
from dsp_permissions_scripts.utils.get_logger import get_logger

logger = get_logger(__name__)


@dataclass
class IRIUpdater(ABC):
iri: str
dsp_client: DspClient
err_msg: str | None = field(init=False, default=None)

@abstractmethod
def update_iri(self, new_scope: PermissionScope) -> None:
pass

@staticmethod
def from_string(string: str, dsp_client: DspClient) -> ResourceIRIUpdater | ValueIRIUpdater:
if re.search(r"^http://rdfh\.ch/[^/]{4}/[^/]{22}/values/[^/]{22}$", string):
return ValueIRIUpdater(string, dsp_client)
elif re.search(r"^http://rdfh\.ch/[^/]{4}/[^/]{22}$", string):
return ResourceIRIUpdater(string, dsp_client)
else:
raise InvalidIRIError(f"Could not parse IRI {string}")

def _get_res_dict(self, res_iri: str) -> dict[str, Any]:
return self.dsp_client.get(f"/v2/resources/{quote_plus(res_iri, safe='')}")


@dataclass
class ResourceIRIUpdater(IRIUpdater):
def update_iri(self, new_scope: PermissionScope) -> None:
res_dict = self._get_res_dict(self.iri)
try:
update_permissions_for_resource(
resource_iri=self.iri,
lmd=res_dict["knora-api:lastModificationDate"],
resource_type=res_dict["@type"],
context=res_dict["@context"] | {"knora-admin": KNORA_ADMIN_ONTO_NAMESPACE},
scope=new_scope,
dsp_client=self.dsp_client,
)
except ApiError as err:
self.err_msg = err.message
logger.error(self.err_msg)


@dataclass
class ValueIRIUpdater(IRIUpdater):
def update_iri(self, new_scope: PermissionScope) -> None:
res_iri = re.sub(r"/values/[^/]{22}$", "", self.iri)
res_dict = self._get_res_dict(res_iri)
val_oap = next((v for v in get_value_oaps(res_dict) if v.value_iri == self.iri), None)
if not val_oap:
self.err_msg = f"Could not find value {self.iri} in resource {res_dict['@id']}"
logger.error(self.err_msg)
return
val_oap.scope = new_scope
try:
update_permissions_for_value(
value=val_oap,
resource_type=res_dict["@type"],
context=res_dict["@context"] | {"knora-admin": KNORA_ADMIN_ONTO_NAMESPACE},
dsp_client=self.dsp_client,
)
except ApiError as err:
self.err_msg = err.message
logger.error(self.err_msg)


def update_iris(
iri_file: Path,
new_scope: PermissionScope,
dsp_client: DspClient,
) -> None:
iri_updaters = _initialize_iri_updaters(iri_file, dsp_client)
for iri in iri_updaters:
iri.update_iri(new_scope)
_tidy_up(iri_updaters, iri_file)


def _initialize_iri_updaters(iri_file: Path, dsp_client: DspClient) -> list[ResourceIRIUpdater | ValueIRIUpdater]:
logger.info(f"Read IRIs from file {iri_file} and initialize IRI updaters...")
iris_raw = {x for x in iri_file.read_text().splitlines() if re.search(r"\w", x)}
iri_updaters = [IRIUpdater.from_string(iri, dsp_client) for iri in iris_raw]
res_counter = sum(isinstance(x, ResourceIRIUpdater) for x in iri_updaters)
val_counter = sum(isinstance(x, ValueIRIUpdater) for x in iri_updaters)
logger.info(
f"Perform {len(iri_updaters)} updates ({res_counter} resources and {val_counter} values) "
f"on server {dsp_client.server}..."
)
return iri_updaters


def _tidy_up(iri_updaters: list[ResourceIRIUpdater | ValueIRIUpdater], iri_file: Path) -> None:
if failed_updaters := [x for x in iri_updaters if x.err_msg]:
failed_iris_file = iri_file.with_stem(f"{iri_file.stem}_failed")
failed_iris_file.write_text("\n".join([f"{x.iri}\t\t{x.err_msg}" for x in failed_updaters]))
logger.info(f"Some updates failed. The failed IRIs and error messages have been saved to {failed_iris_file}.")
else:
logger.info(f"All {len(iri_updaters)} updates were successful.")
31 changes: 31 additions & 0 deletions dsp_permissions_scripts/template_for_single_iris.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pathlib import Path

from dsp_permissions_scripts.models.host import Hosts
from dsp_permissions_scripts.models.scope import OPEN
from dsp_permissions_scripts.oap.update_iris import update_iris
from dsp_permissions_scripts.utils.authentication import login
from dsp_permissions_scripts.utils.get_logger import log_start_of_script


def main() -> None:
"""
Use this script if you want to update the OAPs of resources/values provided in a text file.
The text file should contain the IRIs of the resources/values (one per line) to update.
Resource IRIs and value IRIs can be mixed in the text file.
"""
host = Hosts.get_host("localhost")
shortcode = "4123"
iri_file = Path("project_data/4123/iris_to_update.txt")
new_scope = OPEN
log_start_of_script(host, shortcode)
dsp_client = login(host)

update_iris(
iri_file=iri_file,
new_scope=new_scope,
dsp_client=dsp_client,
)


if __name__ == "__main__":
main()
12 changes: 6 additions & 6 deletions tests/test_oap_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from dsp_permissions_scripts.oap.oap_get import KB_RESCLASSES
from dsp_permissions_scripts.oap.oap_get import _get_oap_of_one_resource
from dsp_permissions_scripts.oap.oap_get import _get_oaps_of_one_kb_resclass
from dsp_permissions_scripts.oap.oap_get import _get_value_oaps
from dsp_permissions_scripts.oap.oap_get import get_oaps_of_kb_resclasses
from dsp_permissions_scripts.oap.oap_get import get_value_oaps
from dsp_permissions_scripts.oap.oap_model import Oap
from dsp_permissions_scripts.oap.oap_model import OapRetrieveConfig
from dsp_permissions_scripts.oap.oap_model import ResourceOap
Expand Down Expand Up @@ -207,7 +207,7 @@ def test_oap_get_multiple_values_per_prop(self) -> None:
resource_iri="http://rdfh.ch/0838/dBu563hjSN6RmJZp6NU3_Q",
),
]
returned = _get_value_oaps(resource)
returned = get_value_oaps(resource)
assert expected == returned

def test_linkobj_full(self, linkobj: dict[str, Any]) -> None:
Expand All @@ -234,7 +234,7 @@ def test_linkobj_full(self, linkobj: dict[str, Any]) -> None:
resource_iri="http://rdfh.ch/F18E/Os_5VvgkSC2saUlSUdcLhA",
)
expected = [exp_1, exp_2, exp_3]
returned = _get_value_oaps(linkobj)
returned = get_value_oaps(linkobj)
assert returned == unordered(expected)

def test_video_segment_full(self, video_segment: dict[str, Any]) -> None:
Expand Down Expand Up @@ -275,7 +275,7 @@ def test_video_segment_full(self, video_segment: dict[str, Any]) -> None:
resource_iri="http://rdfh.ch/0812/l32ehsHuTfaQAKVTRiuBRA",
)
expected = [exp_1, exp_2, exp_3, exp_4, exp_5]
returned = _get_value_oaps(video_segment)
returned = get_value_oaps(video_segment)
assert returned == unordered(expected)

def test_video_segment_restrict_to_1_prop(self, video_segment: dict[str, Any]) -> None:
Expand All @@ -288,7 +288,7 @@ def test_video_segment_restrict_to_1_prop(self, video_segment: dict[str, Any]) -
resource_iri="http://rdfh.ch/0812/l32ehsHuTfaQAKVTRiuBRA",
)
expected = [exp_1]
returned = _get_value_oaps(video_segment, ["knora-api:relatesToValue"])
returned = get_value_oaps(video_segment, ["knora-api:relatesToValue"])
assert returned == unordered(expected)

def test_video_segment_restrict_to_2_props(self, video_segment: dict[str, Any]) -> None:
Expand All @@ -308,7 +308,7 @@ def test_video_segment_restrict_to_2_props(self, video_segment: dict[str, Any])
resource_iri="http://rdfh.ch/0812/l32ehsHuTfaQAKVTRiuBRA",
)
expected = [exp_1, exp_2]
returned = _get_value_oaps(video_segment, ["knora-api:relatesToValue", "knora-api:hasTitle"])
returned = get_value_oaps(video_segment, ["knora-api:relatesToValue", "knora-api:hasTitle"])
assert returned == unordered(expected)


Expand Down
Loading

0 comments on commit 8c3de40

Please sign in to comment.