diff --git a/dsp_permissions_scripts/models/api_error.py b/dsp_permissions_scripts/models/api_error.py index a5faf4bf..9b6e0138 100644 --- a/dsp_permissions_scripts/models/api_error.py +++ b/dsp_permissions_scripts/models/api_error.py @@ -1,16 +1,18 @@ import pprint -from dataclasses import dataclass, field -from typing import Any +from dataclasses import dataclass -@dataclass(frozen=True) +@dataclass class ApiError(Exception): """Exception raised when an error occurs while calling DSP-API.""" message: str response_text: str | None = None status_code: int | None = None - payload: dict[str, Any] = field(default_factory=dict) def __str__(self) -> str: return pprint.pformat(vars(self)) + +@dataclass +class PermissionsAlreadyUpToDate(Exception): + message: str = "The submitted permissions are the same as the current ones" diff --git a/dsp_permissions_scripts/oap/oap_set.py b/dsp_permissions_scripts/oap/oap_set.py index a59f29bd..25d2395c 100644 --- a/dsp_permissions_scripts/oap/oap_set.py +++ b/dsp_permissions_scripts/oap/oap_set.py @@ -5,17 +5,14 @@ from datetime import datetime from typing import Any -import requests - from dsp_permissions_scripts.models.api_error import ApiError from dsp_permissions_scripts.models.scope import PermissionScope from dsp_permissions_scripts.models.value import ValueUpdate from dsp_permissions_scripts.oap.oap_get import get_resource from dsp_permissions_scripts.oap.oap_model import Oap -from dsp_permissions_scripts.utils.authentication import get_protocol from dsp_permissions_scripts.utils.get_logger import get_logger from dsp_permissions_scripts.utils.scope_serialization import create_string_from_scope -from dsp_permissions_scripts.utils.try_request import http_call_with_retry +from dsp_permissions_scripts.utils import connection logger = get_logger(__name__) @@ -44,8 +41,6 @@ def _update_permissions_for_value( resource_type: str, context: dict[str, str], scope: PermissionScope, - host: str, - token: str, ) -> None: """Updates the permissions for the given value (of a property) on a DSP server""" payload = { @@ -58,27 +53,12 @@ def _update_permissions_for_value( }, "@context": context, } - protocol = get_protocol(host) - url = f"{protocol}://{host}/v2/values" - headers = {"Authorization": f"Bearer {token}"} - response = http_call_with_retry( - action=lambda: requests.put(url, headers=headers, json=payload, timeout=20), - err_msg=f"Error while updating permissions of resource {resource_iri}, value {value.value_iri}", - ) - if response.status_code == 400 and response.text: - already = "dsp.errors.BadRequestException: The submitted permissions are the same as the current ones" - if already in response.text: - msg = f"Permissions of resource {resource_iri}, value {value.value_iri} are already up to date" - logger.warning(msg) - elif response.status_code != 200: - raise ApiError( - message=f"Error while updating permissions of resource {resource_iri}, value {value.value_iri}", - response_text=response.text, - status_code=response.status_code, - payload=payload - ) - else: - logger.info(f"Updated permissions of resource {resource_iri}, value {value.value_iri}") + try: + connection.con.put("/v2/values", data=payload) + except ApiError as err: + err.message = f"Error while updating permissions of resource {resource_iri}, value {value.value_iri}" + raise err from None + logger.info(f"Updated permissions of resource {resource_iri}, value {value.value_iri}") def _update_permissions_for_resource( @@ -88,7 +68,6 @@ def _update_permissions_for_resource( context: dict[str, str], scope: PermissionScope, host: str, - token: str, ) -> None: """Updates the permissions for the given resource on a DSP server""" payload = { @@ -99,20 +78,11 @@ def _update_permissions_for_resource( } if lmd: payload["knora-api:lastModificationDate"] = lmd - protocol = get_protocol(host) - url = f"{protocol}://{host}/v2/resources" - headers = {"Authorization": f"Bearer {token}"} - response = http_call_with_retry( - action=lambda: requests.put(url, headers=headers, json=payload, timeout=20), - err_msg=f"ERROR while updating permissions of resource {resource_iri}", - ) - if response.status_code != 200: - raise ApiError( - message=f"ERROR while updating permissions of resource {resource_iri}", - response_text=response.text, - status_code=response.status_code, - payload=payload, - ) + try: + connection.con.put("/v2/resources", data=payload) + except ApiError as err: + err.message = f"ERROR while updating permissions of resource {resource_iri}" + raise err from None logger.info(f"Updated permissions of resource {resource_iri}") @@ -120,7 +90,6 @@ def _update_permissions_for_resource_and_values( resource_iri: str, scope: PermissionScope, host: str, - token: str, ) -> tuple[str, bool]: """Updates the permissions for the given resource and its values on a DSP server""" try: @@ -140,7 +109,6 @@ def _update_permissions_for_resource_and_values( context=resource["@context"], scope=scope, host=host, - token=token, ) except ApiError as err: logger.error(err) @@ -155,8 +123,6 @@ def _update_permissions_for_resource_and_values( resource_type=resource["@type"], context=resource["@context"], scope=scope, - host=host, - token=token, ) except ApiError as err: logger.error(err) @@ -180,7 +146,6 @@ def _write_failed_res_iris_to_file( def _launch_thread_pool( resource_oaps: list[Oap], host: str, - token: str, nthreads: int, ) -> list[str]: counter = 0 @@ -193,7 +158,6 @@ def _launch_thread_pool( resource_oap.object_iri, resource_oap.scope, host, - token, ) for resource_oap in resource_oaps ] for result in as_completed(jobs): @@ -212,7 +176,6 @@ def _launch_thread_pool( def apply_updated_oaps_on_server( resource_oaps: list[Oap], host: str, - token: str, shortcode: str, nthreads: int = 4, ) -> None: @@ -227,7 +190,7 @@ def apply_updated_oaps_on_server( logger.info(f"******* Updating OAPs of {len(resource_oaps)} resources on {host} *******") print(f"******* Updating OAPs of {len(resource_oaps)} resources on {host} *******") - failed_res_iris = _launch_thread_pool(resource_oaps, host, token, nthreads) + failed_res_iris = _launch_thread_pool(resource_oaps, host, nthreads) if failed_res_iris: timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") diff --git a/dsp_permissions_scripts/template.py b/dsp_permissions_scripts/template.py index 7fb5c49a..b6c0289a 100644 --- a/dsp_permissions_scripts/template.py +++ b/dsp_permissions_scripts/template.py @@ -91,7 +91,6 @@ def update_aps( def update_doaps( host: str, shortcode: str, - token: str, ) -> None: """Sample function to modify the Default Object Access Permissions of a project.""" project_doaps = get_doaps_of_project( @@ -124,7 +123,6 @@ def update_doaps( def update_oaps( host: str, shortcode: str, - token: str, ) -> None: """Sample function to modify the Object Access Permissions of a project.""" resource_oaps = get_all_resource_oaps_of_project(shortcode) @@ -137,7 +135,6 @@ def update_oaps( apply_updated_oaps_on_server( resource_oaps=resource_oaps_modified, host=host, - token=token, shortcode=shortcode, nthreads=4, ) @@ -171,12 +168,10 @@ def main() -> None: update_doaps( host=host, shortcode=shortcode, - token=token, ) update_oaps( host=host, shortcode=shortcode, - token=token, ) diff --git a/dsp_permissions_scripts/utils/connection.py b/dsp_permissions_scripts/utils/connection.py index cace1fc6..39d0f8a2 100644 --- a/dsp_permissions_scripts/utils/connection.py +++ b/dsp_permissions_scripts/utils/connection.py @@ -16,7 +16,7 @@ from requests import Response from requests import Session -from dsp_permissions_scripts.models.api_error import ApiError +from dsp_permissions_scripts.models.api_error import ApiError, PermissionsAlreadyUpToDate from dsp_permissions_scripts.utils.get_logger import get_logger logger = get_logger(__name__) @@ -80,7 +80,7 @@ def login(self, email: str, password: str) -> None: password: password of the user Raises: - UserError: if DSP-API returns no token with the provided user credentials + ApiError: if DSP-API returns no token with the provided user credentials """ response = self.post( route="/v2/authentication", @@ -119,7 +119,7 @@ def post( response from server Raises: - PermanentConnectionError: if the server returns a permanent error + ApiError: if the server returns a permanent error """ if data: headers = headers or {} @@ -145,7 +145,7 @@ def get( response from server Raises: - PermanentConnectionError: if the server returns a permanent error + ApiError: if the server returns a permanent error """ params = RequestParameters("GET", self._make_url(route), self.timeout, headers=headers) response = self._try_network_action(params) @@ -169,7 +169,8 @@ def put( response from server Raises: - PermanentConnectionError: if the server returns a permanent error + ApiError: if the server returns a permanent error + PermissionsAlreadyUpToDate: if the permissions are already up to date """ if data: headers = headers or {} @@ -195,7 +196,7 @@ def delete( response from server Raises: - PermanentConnectionError: if the server returns a permanent error + ApiError: if the server returns a permanent error """ params = RequestParameters("DELETE", self._make_url(route), self.timeout, headers=headers) response = self._try_network_action(params) @@ -218,8 +219,8 @@ def _try_network_action(self, params: RequestParameters) -> Response: params: keyword arguments for the HTTP request Raises: - BadCredentialsError: if the server returns a 401 status code on the route /v2/authentication - PermanentConnectionError: if the server returns a permanent error + ApiError: if the server returns a permanent error + PermissionsAlreadyUpToDate: if the permissions are already up to date unexpected exceptions: if the action fails with an unexpected exception Returns: @@ -230,8 +231,8 @@ def _try_network_action(self, params: RequestParameters) -> Response: try: self._log_request(params) response = action() - except (TimeoutError, ReadTimeout) as err: - self._log_and_raise_timeouts(err) + except (TimeoutError, ReadTimeout): + self._log_and_sleep(reason="Timeout Error raised", retry_counter=i, exc_info=True) except (ConnectionError, RequestException): self._renew_session() self._log_and_sleep(reason="Connection Error raised", retry_counter=i, exc_info=True) @@ -253,8 +254,13 @@ def _handle_non_ok_responses(self, response: Response, retry_counter: int) -> No if should_retry: self._log_and_sleep("Transient Error", retry_counter, exc_info=False) return None - else: - raise ApiError("Permanently unable to execute the network action", response.text, response.status_code) + + already = "dsp.errors.BadRequestException: The submitted permissions are the same as the current ones" + should_break = response.status_code == 400 and response.text and already in response.text + if should_break: + raise PermissionsAlreadyUpToDate() + + raise ApiError("Permanently unable to execute the network action", response.text, response.status_code) def _renew_session(self) -> None: self.session.close() @@ -269,12 +275,6 @@ def _log_and_sleep(self, reason: str, retry_counter: int, exc_info: bool) -> Non logger.error(f"{msg} ({retry_counter=:})", exc_info=exc_info) time.sleep(2**retry_counter) - def _log_and_raise_timeouts(self, error: TimeoutError | ReadTimeout) -> None: - msg = f"A '{error.__class__.__name__}' occurred during the connection to the DSP server." - print(f"{datetime.now()}: {msg}") - logger.exception(msg) - raise ApiError(msg) from None - def _log_response(self, response: Response) -> None: dumpobj: dict[str, Any] = { "status_code": response.status_code,