From 9b7ae0507df8e5df0fa44186115e6a7c8d76f0f5 Mon Sep 17 00:00:00 2001 From: kingspp Date: Wed, 29 Jan 2025 14:57:40 +0530 Subject: [PATCH 1/4] Added support for Roles API (Non-AERO) --- pycaprio/core/adapters/http_adapter.py | 18 +++++++++++- pycaprio/core/clients/retryable_client.py | 4 +-- pycaprio/core/interfaces/adapter.py | 34 +++++++++++++++++++++++ pycaprio/core/interfaces/client.py | 2 +- pycaprio/core/mappings.py | 7 +++++ pycaprio/core/objects/role.py | 20 +++++++++++++ pycaprio/core/schemas/role.py | 24 ++++++++++++++++ pycaprio/mappings.py | 2 +- 8 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 pycaprio/core/objects/role.py create mode 100644 pycaprio/core/schemas/role.py diff --git a/pycaprio/core/adapters/http_adapter.py b/pycaprio/core/adapters/http_adapter.py index 8b5f891..64f3072 100644 --- a/pycaprio/core/adapters/http_adapter.py +++ b/pycaprio/core/adapters/http_adapter.py @@ -1,7 +1,6 @@ from typing import IO, Union from typing import List from typing import Optional - from pycaprio.core.clients.retryable_client import RetryableInceptionClient from pycaprio.core.interfaces.adapter import BaseInceptionAdapter from pycaprio.core.interfaces.client import BaseInceptionClient @@ -9,6 +8,7 @@ from pycaprio.core.mappings import AnnotationState from pycaprio.core.mappings import InceptionFormat from pycaprio.core.mappings import DocumentState +from pycaprio.core.objects.role import Role from pycaprio.core.objects.annotation import Annotation from pycaprio.core.objects.document import Document from pycaprio.core.objects.project import Project @@ -17,6 +17,7 @@ from pycaprio.core.schemas.document import DocumentSchema from pycaprio.core.schemas.project import ProjectSchema from pycaprio.core.schemas.curation import CurationSchema +from pycaprio.core.schemas.role import RoleSchema class HttpInceptionAdapter(BaseInceptionAdapter): @@ -174,6 +175,21 @@ def delete_curation(self, project: Union[Project, int], document: Union[Document self.client.delete(f'/projects/{project_id}/documents/{document_id}/curation') return True + def list_roles(self, project: Union[Project, int], userId:str) -> list[Role] | Role: + project_id = self._get_object_id(project) + response = self.client.get(f'/projects/{project_id}/permissions/{userId}') + return RoleSchema().load(response.json()['body'], many=True) + + def assign_roles(self, project: Union[Project, int], userId:str, roles:List[str]) -> list[Role] | Role: + project_id = self._get_object_id(project) + response = self.client.post(f'/projects/{project_id}/permissions/{userId}', data={'roles': roles}) + return RoleSchema().load(response.json()['body'], many=True) + + def delete_roles(self, project: Union[Project, int], userId:str, roles:List[str]) -> list[Role] | Role: + project_id = self._get_object_id(project) + response = self.client.delete(f'/projects/{project_id}/permissions/{userId}', data={'roles': roles}) + return RoleSchema().load(response.json()['body'], many=True) + @staticmethod def _get_object_id(o: Union[int, Project, Document, Annotation]) -> int: object_id_mappings = { diff --git a/pycaprio/core/clients/retryable_client.py b/pycaprio/core/clients/retryable_client.py index c6f4a53..2188e7d 100644 --- a/pycaprio/core/clients/retryable_client.py +++ b/pycaprio/core/clients/retryable_client.py @@ -31,8 +31,8 @@ def post(self, url: str, data: Optional[dict] = None, form_data: Optional[dict] files: Optional[dict] = None) -> requests.Response: return self.request('post', url, data=data, form_data=form_data, files=files) - def delete(self, url: str) -> requests.Response: - return self.request('delete', url) + def delete(self, url: str, data: Optional[dict] = None) -> requests.Response: + return self.request('delete', url, data=data) def request(self, method: str, url: str, **kwargs) -> requests.Response: retries = 0 diff --git a/pycaprio/core/interfaces/adapter.py b/pycaprio/core/interfaces/adapter.py index 83fa8dd..6deda1b 100644 --- a/pycaprio/core/interfaces/adapter.py +++ b/pycaprio/core/interfaces/adapter.py @@ -7,10 +7,12 @@ from pycaprio.core.mappings import AnnotationState from pycaprio.core.mappings import InceptionFormat from pycaprio.core.mappings import DocumentState +from pycaprio.core.mappings import RoleType from pycaprio.core.objects.annotation import Annotation from pycaprio.core.objects.document import Document from pycaprio.core.objects.project import Project from pycaprio.core.objects.curation import Curation +from pycaprio.core.objects.role import RoleType class BaseInceptionAdapter(metaclass=ABCMeta): @@ -224,3 +226,35 @@ def delete_curation(self, project: Union[Project, int], document: Union[Document :param project: Project/Project id. """ pass # pragma: no cover + + + @abstractmethod + def list_roles(self, project: Union[Project, int], userId: str) -> List[RoleType]: + """ + List permissions for a user in the given project (non AERO) + :param project: Project/Project id. + :param userId: Username. + """ + pass # pragma: no cover + + + @abstractmethod + def assign_roles(self, project: Union[Project, int], userId: str, role:List[RoleType]) -> List[RoleType]: + """ + Assign roles to a user in the given project (non-AERO) + :param project: Project/Project id. + :param userId: Username. + :param role: List of Roles [See PermissionRoles in mappings] + """ + pass # pragma: no cover + + + @abstractmethod + def delete_roles(self, project: Union[Project, int], userId: str, role:List[RoleType]) -> List[RoleType]: + """ + Delete roles to a user in the given project (non-AERO) + :param project: Project/Project id. + :param userId: Username. + :param role: List of Roles [See PermissionRoles in mappings] + """ + pass # pragma: no cover diff --git a/pycaprio/core/interfaces/client.py b/pycaprio/core/interfaces/client.py index acff1d5..49dd5f8 100644 --- a/pycaprio/core/interfaces/client.py +++ b/pycaprio/core/interfaces/client.py @@ -54,7 +54,7 @@ def post(self, url: str, json: dict, files: dict) -> requests.Response: pass # pragma: no cover @abstractmethod - def delete(self, url: str) -> requests.Response: + def delete(self, url: str, json:dict) -> requests.Response: """ Issues an authenticated DELETE request to Inception :param url: relative url to make request diff --git a/pycaprio/core/mappings.py b/pycaprio/core/mappings.py index 6d82986..aeb6815 100644 --- a/pycaprio/core/mappings.py +++ b/pycaprio/core/mappings.py @@ -59,3 +59,10 @@ class DocumentState: ANNOTATION_COMPLETE = 'ANNOTATION-COMPLETE' CURATION_IN_PROGRESS = 'CURATION-IN-PROGRESS' CURATION_COMPLETE = 'CURATION-COMPLETE' + + +class RoleType: + # https://github.com/inception-project/inception/blob/main/inception/inception-model/src/main/java/de/tudarmstadt/ukp/clarin/webanno/model/PermissionLevel.java + ANNOTATOR = "ANNOTATOR" + CURATOR = "CURATOR" + MANAGER = "MANAGER" diff --git a/pycaprio/core/objects/role.py b/pycaprio/core/objects/role.py new file mode 100644 index 0000000..27b83ae --- /dev/null +++ b/pycaprio/core/objects/role.py @@ -0,0 +1,20 @@ +__any__ = ['Role'] + +import typing +from pycaprio.core.mappings import RoleType + +class Role: + """ + INCEpTION's Permissions object + """ + + def __init__(self, project_id: int, userId:str, roles:typing.List[RoleType]): + self.project_id = project_id + self.userId = userId + self.roles = roles + + def __repr__(self): + return f"" + + def __str__(self): + return repr(self) diff --git a/pycaprio/core/schemas/role.py b/pycaprio/core/schemas/role.py new file mode 100644 index 0000000..420d9af --- /dev/null +++ b/pycaprio/core/schemas/role.py @@ -0,0 +1,24 @@ +from typing import Union +from typing import List +from pycaprio.core.interfaces.schema import BaseInceptionSchema +from pycaprio.core.objects.role import Role + +serialized_type = Union[List[dict], dict] +deserialized_type = Union[List[Role], Role] + + +class RoleSchema(BaseInceptionSchema): + """ + Schema to serialize/deserialize Roles. + Documentation is described in 'BaseInceptionSchema'. + """ + + def load(self, roles_dict: serialized_type, many: bool = False) -> deserialized_type: + if many: + return [self.load(project, many=False) for project in roles_dict] + return Role(project_id=roles_dict['project'], userId=roles_dict['user'], roles=roles_dict['role']) + + def dump(self, roles: deserialized_type, many: bool = False) -> serialized_type: + if many: + return [self.dump(p, many=False) for p in roles] + return {'projectId': roles.project_id, 'user': roles.userId, 'role':roles.roles} diff --git a/pycaprio/mappings.py b/pycaprio/mappings.py index 8b86e11..379fad9 100644 --- a/pycaprio/mappings.py +++ b/pycaprio/mappings.py @@ -1,2 +1,2 @@ # flake8: noqa -from pycaprio.core.mappings import InceptionFormat, DocumentState, AnnotationState # pragma: no cover +from pycaprio.core.mappings import InceptionFormat, DocumentState, AnnotationState, RoleType # pragma: no cover From 70826d39839a021647f4331d5bb8c7a9eb27071e Mon Sep 17 00:00:00 2001 From: kingspp Date: Thu, 30 Jan 2025 00:13:51 +0530 Subject: [PATCH 2/4] Update init --- pycaprio/core/objects/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pycaprio/core/objects/__init__.py b/pycaprio/core/objects/__init__.py index 22d27ee..400461d 100644 --- a/pycaprio/core/objects/__init__.py +++ b/pycaprio/core/objects/__init__.py @@ -3,3 +3,4 @@ from pycaprio.core.objects.document import Document from pycaprio.core.objects.project import Project from pycaprio.core.objects.curation import Curation +from pycaprio.core.objects.role import Role From ed673edcb9d1ae530e416f60f315ba495ecda12a Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Tue, 25 Feb 2025 15:55:16 +0100 Subject: [PATCH 3/4] Format and fix linter issues --- pycaprio/core/adapters/http_adapter.py | 18 +++++++++--------- pycaprio/core/clients/retryable_client.py | 4 ++-- pycaprio/core/interfaces/adapter.py | 8 ++------ pycaprio/core/interfaces/client.py | 2 +- pycaprio/core/objects/role.py | 5 +++-- pycaprio/core/schemas/role.py | 4 ++-- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/pycaprio/core/adapters/http_adapter.py b/pycaprio/core/adapters/http_adapter.py index c4c8e3f..ba53667 100644 --- a/pycaprio/core/adapters/http_adapter.py +++ b/pycaprio/core/adapters/http_adapter.py @@ -216,20 +216,20 @@ def delete_curation(self, project: Union[Project, int], document: Union[Document self.client.delete(f"/projects/{project_id}/documents/{document_id}/curation") return True - def list_roles(self, project: Union[Project, int], userId:str) -> list[Role] | Role: + def list_roles(self, project: Union[Project, int], userId: str) -> list[Role] | Role: project_id = self._get_object_id(project) - response = self.client.get(f'/projects/{project_id}/permissions/{userId}') - return RoleSchema().load(response.json()['body'], many=True) + response = self.client.get(f"/projects/{project_id}/permissions/{userId}") + return RoleSchema().load(response.json()["body"], many=True) - def assign_roles(self, project: Union[Project, int], userId:str, roles:List[str]) -> list[Role] | Role: + def assign_roles(self, project: Union[Project, int], userId: str, roles: List[str]) -> list[Role] | Role: project_id = self._get_object_id(project) - response = self.client.post(f'/projects/{project_id}/permissions/{userId}', data={'roles': roles}) - return RoleSchema().load(response.json()['body'], many=True) + response = self.client.post(f"/projects/{project_id}/permissions/{userId}", data={"roles": roles}) + return RoleSchema().load(response.json()["body"], many=True) - def delete_roles(self, project: Union[Project, int], userId:str, roles:List[str]) -> list[Role] | Role: + def delete_roles(self, project: Union[Project, int], userId: str, roles: List[str]) -> list[Role] | Role: project_id = self._get_object_id(project) - response = self.client.delete(f'/projects/{project_id}/permissions/{userId}', data={'roles': roles}) - return RoleSchema().load(response.json()['body'], many=True) + response = self.client.delete(f"/projects/{project_id}/permissions/{userId}", data={"roles": roles}) + return RoleSchema().load(response.json()["body"], many=True) @staticmethod def _get_object_id(o: Union[int, Project, Document, Annotation]) -> int: diff --git a/pycaprio/core/clients/retryable_client.py b/pycaprio/core/clients/retryable_client.py index 23b82e6..6f9bc8e 100644 --- a/pycaprio/core/clients/retryable_client.py +++ b/pycaprio/core/clients/retryable_client.py @@ -33,8 +33,8 @@ def post( ) -> requests.Response: return self.request("post", url, data=data, form_data=form_data, files=files) - def delete(self, url: str, data: Optional[dict] = None) -> requests.Response: - return self.request('delete', url, data=data) + def delete(self, url: str, data: Optional[dict] = None) -> requests.Response: + return self.request("delete", url, data=data) def request(self, method: str, url: str, **kwargs) -> requests.Response: retries = 0 diff --git a/pycaprio/core/interfaces/adapter.py b/pycaprio/core/interfaces/adapter.py index 2a1fedf..18e48a7 100644 --- a/pycaprio/core/interfaces/adapter.py +++ b/pycaprio/core/interfaces/adapter.py @@ -7,7 +7,6 @@ from pycaprio.core.mappings import AnnotationState from pycaprio.core.mappings import InceptionFormat from pycaprio.core.mappings import DocumentState -from pycaprio.core.mappings import RoleType from pycaprio.core.objects.annotation import Annotation from pycaprio.core.objects.document import Document from pycaprio.core.objects.project import Project @@ -253,7 +252,6 @@ def delete_curation(self, project: Union[Project, int], document: Union[Document """ pass # pragma: no cover - @abstractmethod def list_roles(self, project: Union[Project, int], userId: str) -> List[RoleType]: """ @@ -263,9 +261,8 @@ def list_roles(self, project: Union[Project, int], userId: str) -> List[RoleType """ pass # pragma: no cover - @abstractmethod - def assign_roles(self, project: Union[Project, int], userId: str, role:List[RoleType]) -> List[RoleType]: + def assign_roles(self, project: Union[Project, int], userId: str, role: List[RoleType]) -> List[RoleType]: """ Assign roles to a user in the given project (non-AERO) :param project: Project/Project id. @@ -274,9 +271,8 @@ def assign_roles(self, project: Union[Project, int], userId: str, role:List[Role """ pass # pragma: no cover - @abstractmethod - def delete_roles(self, project: Union[Project, int], userId: str, role:List[RoleType]) -> List[RoleType]: + def delete_roles(self, project: Union[Project, int], userId: str, role: List[RoleType]) -> List[RoleType]: """ Delete roles to a user in the given project (non-AERO) :param project: Project/Project id. diff --git a/pycaprio/core/interfaces/client.py b/pycaprio/core/interfaces/client.py index ec3ab61..35c5017 100644 --- a/pycaprio/core/interfaces/client.py +++ b/pycaprio/core/interfaces/client.py @@ -54,7 +54,7 @@ def post(self, url: str, json: dict, files: dict) -> requests.Response: pass # pragma: no cover @abstractmethod - def delete(self, url: str, json:dict) -> requests.Response: + def delete(self, url: str, json: dict) -> requests.Response: """ Issues an authenticated DELETE request to Inception :param url: relative url to make request diff --git a/pycaprio/core/objects/role.py b/pycaprio/core/objects/role.py index 27b83ae..534194c 100644 --- a/pycaprio/core/objects/role.py +++ b/pycaprio/core/objects/role.py @@ -1,14 +1,15 @@ -__any__ = ['Role'] +__any__ = ["Role"] import typing from pycaprio.core.mappings import RoleType + class Role: """ INCEpTION's Permissions object """ - def __init__(self, project_id: int, userId:str, roles:typing.List[RoleType]): + def __init__(self, project_id: int, userId: str, roles: typing.List[RoleType]): self.project_id = project_id self.userId = userId self.roles = roles diff --git a/pycaprio/core/schemas/role.py b/pycaprio/core/schemas/role.py index 420d9af..e31dc4b 100644 --- a/pycaprio/core/schemas/role.py +++ b/pycaprio/core/schemas/role.py @@ -16,9 +16,9 @@ class RoleSchema(BaseInceptionSchema): def load(self, roles_dict: serialized_type, many: bool = False) -> deserialized_type: if many: return [self.load(project, many=False) for project in roles_dict] - return Role(project_id=roles_dict['project'], userId=roles_dict['user'], roles=roles_dict['role']) + return Role(project_id=roles_dict["project"], userId=roles_dict["user"], roles=roles_dict["role"]) def dump(self, roles: deserialized_type, many: bool = False) -> serialized_type: if many: return [self.dump(p, many=False) for p in roles] - return {'projectId': roles.project_id, 'user': roles.userId, 'role':roles.roles} + return {"projectId": roles.project_id, "user": roles.userId, "role": roles.roles} From ff3a0bd156be5de0b478ff602377f414ad21c17b Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Tue, 25 Feb 2025 16:56:10 +0100 Subject: [PATCH 4/4] Fix parameter name and error --- pycaprio/core/adapters/http_adapter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pycaprio/core/adapters/http_adapter.py b/pycaprio/core/adapters/http_adapter.py index ba53667..3f901f4 100644 --- a/pycaprio/core/adapters/http_adapter.py +++ b/pycaprio/core/adapters/http_adapter.py @@ -216,19 +216,19 @@ def delete_curation(self, project: Union[Project, int], document: Union[Document self.client.delete(f"/projects/{project_id}/documents/{document_id}/curation") return True - def list_roles(self, project: Union[Project, int], userId: str) -> list[Role] | Role: + def list_roles(self, project: Union[Project, int], user_id: str) -> List[Role]: project_id = self._get_object_id(project) - response = self.client.get(f"/projects/{project_id}/permissions/{userId}") + response = self.client.get(f"/projects/{project_id}/permissions/{user_id}") return RoleSchema().load(response.json()["body"], many=True) - def assign_roles(self, project: Union[Project, int], userId: str, roles: List[str]) -> list[Role] | Role: + def assign_roles(self, project: Union[Project, int], user_id: str, roles: List[str]) -> List[Role]: project_id = self._get_object_id(project) - response = self.client.post(f"/projects/{project_id}/permissions/{userId}", data={"roles": roles}) + response = self.client.post(f"/projects/{project_id}/permissions/{user_id}", data={"roles": roles}) return RoleSchema().load(response.json()["body"], many=True) - def delete_roles(self, project: Union[Project, int], userId: str, roles: List[str]) -> list[Role] | Role: + def delete_roles(self, project: Union[Project, int], user_id: str, roles: List[str]) -> List[Role]: project_id = self._get_object_id(project) - response = self.client.delete(f"/projects/{project_id}/permissions/{userId}", data={"roles": roles}) + response = self.client.delete(f"/projects/{project_id}/permissions/{user_id}", data={"roles": roles}) return RoleSchema().load(response.json()["body"], many=True) @staticmethod