diff --git a/dsp_permissions_scripts/models/group.py b/dsp_permissions_scripts/models/group.py index 8261b9df..474f7a1b 100644 --- a/dsp_permissions_scripts/models/group.py +++ b/dsp_permissions_scripts/models/group.py @@ -2,7 +2,6 @@ import re from typing import Any -from typing import Iterable from typing import Self from typing import TypeAlias from typing import Union @@ -23,6 +22,59 @@ PREFIXED_IRI_REGEX = r"^[\w-]+:[\w -]+$" +class BuiltinGroup(BaseModel): + model_config = ConfigDict(frozen=True) + + prefixed_iri: str + + @model_validator(mode="after") + def _check_regex(self) -> Self: + valid_group_names = ["SystemAdmin", "Creator", "ProjectAdmin", "ProjectMember", "KnownUser", "UnknownUser"] + prefix, name = self.prefixed_iri.split(":") + if prefix != "knora-admin" or name not in valid_group_names: + raise InvalidGroupError(f"{self.prefixed_iri} is not a valid group IRI") + return self + + +class CustomGroup(BaseModel): + model_config = ConfigDict(frozen=True) + + prefixed_iri: str + + @model_validator(mode="after") + def _check_regex(self) -> Self: + if not is_prefixed_group_iri(self.prefixed_iri): + raise InvalidGroupError(f"{self.prefixed_iri} is not a valid group IRI") + if self.prefixed_iri.startswith(("knora-admin:", "knora-base:", "knora-api:")): + raise InvalidGroupError(f"{self.prefixed_iri} is not a custom group") + return self + + +def group_builder(prefixed_iri: str) -> BuiltinGroup | CustomGroup: + if prefixed_iri.startswith("knora-admin:"): + return BuiltinGroup(prefixed_iri=prefixed_iri) + elif re.search(PREFIXED_IRI_REGEX, prefixed_iri): + return CustomGroup(prefixed_iri=prefixed_iri) + else: + raise InvalidGroupError(f"{prefixed_iri} is not a valid group IRI") + + +def group_discriminator(v: Any) -> str: + if isinstance(v, dict): + return "builtin" if v["prefixed_iri"].startswith("knora-admin:") else "custom" + else: + return "builtin" if getattr(v, "prefixed_iri").startswith("knora-admin:") else "custom" + + +GroupType: TypeAlias = Annotated[ + Union[ + Annotated[BuiltinGroup, Tag("builtin")], + Annotated[CustomGroup, Tag("custom")], + ], + Discriminator(group_discriminator), +] + + def is_prefixed_group_iri(iri: str) -> bool: if iri.startswith((KNORA_ADMIN_ONTO_NAMESPACE, "http://rdfh.ch/groups/", "knora-base:", "knora-api:")): return False @@ -77,81 +129,6 @@ def _get_full_iri_from_custom_group(prefix: str, groupname: str, dsp_client: Dsp return full_iri -def group_builder(prefixed_iri: str) -> BuiltinGroup | CustomGroup: - if prefixed_iri.startswith("knora-admin:"): - return BuiltinGroup(prefixed_iri=prefixed_iri) - elif re.search(PREFIXED_IRI_REGEX, prefixed_iri): - return CustomGroup(prefixed_iri=prefixed_iri) - else: - raise InvalidGroupError(f"{prefixed_iri} is not a valid group IRI") - - -class BuiltinGroup(BaseModel): - model_config = ConfigDict(frozen=True) - - prefixed_iri: str - - @model_validator(mode="after") - def _check_regex(self) -> Self: - valid_group_names = ["SystemAdmin", "Creator", "ProjectAdmin", "ProjectMember", "KnownUser", "UnknownUser"] - prefix, name = self.prefixed_iri.split(":") - if prefix != "knora-admin" or name not in valid_group_names: - raise InvalidGroupError(f"{self.prefixed_iri} is not a valid group IRI") - return self - - -class CustomGroup(BaseModel): - model_config = ConfigDict(frozen=True) - - prefixed_iri: str - - @model_validator(mode="after") - def _check_regex(self) -> Self: - if not is_prefixed_group_iri(self.prefixed_iri): - raise InvalidGroupError(f"{self.prefixed_iri} is not a valid group IRI") - if self.prefixed_iri.startswith(("knora-admin:", "knora-base:", "knora-api:")): - raise InvalidGroupError(f"{self.prefixed_iri} is not a custom group") - return self - - -def group_discriminator(v: Any) -> str: - if isinstance(v, dict): - return "builtin" if v["prefixed_iri"].startswith("knora-admin:") else "custom" - else: - return "builtin" if getattr(v, "prefixed_iri").startswith("knora-admin:") else "custom" - - -GroupType: TypeAlias = Annotated[ - Union[ - Annotated[BuiltinGroup, Tag("builtin")], - Annotated[CustomGroup, Tag("custom")], - ], - Discriminator(group_discriminator), -] - - -def _get_sort_pos_of_custom_group(prefixed_iri: str) -> int: - alphabet = list("abcdefghijklmnopqrstuvwxyz") - relevant_letter = prefixed_iri.split(":")[-1][0] - return alphabet.index(relevant_letter.lower()) + 99 # must be higher than the highest index of the builtin groups - - -def sort_groups(groups_original: Iterable[GroupType]) -> list[GroupType]: - """ - Sorts groups: - - First according to their power (most powerful first - only applicable for built-in groups) - - Then alphabetically (custom groups) - """ - sort_key = [SYSTEM_ADMIN, CREATOR, PROJECT_ADMIN, PROJECT_MEMBER, KNOWN_USER, UNKNOWN_USER] - groups = list(groups_original) - groups.sort( - key=lambda x: sort_key.index(x) - if isinstance(x, BuiltinGroup) - else _get_sort_pos_of_custom_group(x.prefixed_iri) - ) - return groups - - UNKNOWN_USER = BuiltinGroup(prefixed_iri="knora-admin:UnknownUser") KNOWN_USER = BuiltinGroup(prefixed_iri="knora-admin:KnownUser") PROJECT_MEMBER = BuiltinGroup(prefixed_iri="knora-admin:ProjectMember") diff --git a/dsp_permissions_scripts/models/group_utils.py b/dsp_permissions_scripts/models/group_utils.py new file mode 100644 index 00000000..d0932e10 --- /dev/null +++ b/dsp_permissions_scripts/models/group_utils.py @@ -0,0 +1,32 @@ +from typing import Iterable + +from dsp_permissions_scripts.models.group import CREATOR +from dsp_permissions_scripts.models.group import KNOWN_USER +from dsp_permissions_scripts.models.group import PROJECT_ADMIN +from dsp_permissions_scripts.models.group import PROJECT_MEMBER +from dsp_permissions_scripts.models.group import SYSTEM_ADMIN +from dsp_permissions_scripts.models.group import UNKNOWN_USER +from dsp_permissions_scripts.models.group import BuiltinGroup +from dsp_permissions_scripts.models.group import GroupType + + +def _get_sort_pos_of_custom_group(prefixed_iri: str) -> int: + alphabet = list("abcdefghijklmnopqrstuvwxyz") + relevant_letter = prefixed_iri.split(":")[-1][0] + return alphabet.index(relevant_letter.lower()) + 99 # must be higher than the highest index of the builtin groups + + +def sort_groups(groups_original: Iterable[GroupType]) -> list[GroupType]: + """ + Sorts groups: + - First according to their power (most powerful first - only applicable for built-in groups) + - Then alphabetically (custom groups) + """ + sort_key = [SYSTEM_ADMIN, CREATOR, PROJECT_ADMIN, PROJECT_MEMBER, KNOWN_USER, UNKNOWN_USER] + groups = list(groups_original) + groups.sort( + key=lambda x: sort_key.index(x) + if isinstance(x, BuiltinGroup) + else _get_sort_pos_of_custom_group(x.prefixed_iri) + ) + return groups diff --git a/dsp_permissions_scripts/utils/scope_serialization.py b/dsp_permissions_scripts/utils/scope_serialization.py index f4ed1182..ce97c8ab 100644 --- a/dsp_permissions_scripts/utils/scope_serialization.py +++ b/dsp_permissions_scripts/utils/scope_serialization.py @@ -1,7 +1,7 @@ from typing import Any from dsp_permissions_scripts.models.group import get_full_iri_from_prefixed_iri -from dsp_permissions_scripts.models.group import sort_groups +from dsp_permissions_scripts.models.group_utils import sort_groups from dsp_permissions_scripts.models.scope import PermissionScope from dsp_permissions_scripts.utils.dsp_client import DspClient diff --git a/tests/test_group.py b/tests/test_group.py index ce28abf8..6e56ec16 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -21,7 +21,7 @@ from dsp_permissions_scripts.models.group import get_prefixed_iri_from_full_iri from dsp_permissions_scripts.models.group import group_builder from dsp_permissions_scripts.models.group import is_prefixed_group_iri -from dsp_permissions_scripts.models.group import sort_groups +from dsp_permissions_scripts.models.group_utils import sort_groups from dsp_permissions_scripts.utils.dsp_client import DspClient