diff --git a/src/biodm/component.py b/src/biodm/component.py index c82adab..7ba4f77 100644 --- a/src/biodm/component.py +++ b/src/biodm/component.py @@ -19,7 +19,6 @@ def __init__(self, app: Api): class CRUDApiComponent(ApiComponent, ABC): """API CRUD component Interface. Enforces CRUD methods on children classes.""" - # pylint: disable=W0221 @abstractmethod async def create(self, *args, **kwargs) -> Response: raise NotImplementedError diff --git a/src/biodm/components/controllers/__init__.py b/src/biodm/components/controllers/__init__.py index 9d47216..b2d7caf 100644 --- a/src/biodm/components/controllers/__init__.py +++ b/src/biodm/components/controllers/__init__.py @@ -1,3 +1,5 @@ +"""Api Componenents exposing routes. +Also responsible for validation and serialization and openapi schema generation.""" from .controller import Controller, EntityController, HttpMethod from .resourcecontroller import ResourceController, overload_docstring from .admincontroller import AdminController diff --git a/src/biodm/components/controllers/admincontroller.py b/src/biodm/components/controllers/admincontroller.py index 968c2af..5ae799b 100644 --- a/src/biodm/components/controllers/admincontroller.py +++ b/src/biodm/components/controllers/admincontroller.py @@ -19,30 +19,14 @@ def __init__( schema: Schema = None, read_public: bool = True, ): + self.create = admin_required(self.create) + self.update = admin_required(self.update) + self.delete = admin_required(self.delete) + self.create_update = admin_required(self.create_update) + if not read_public: self.read = admin_required(self.create) - self.query = admin_required(self.query) - - super().__init__(entity, table, schema) - - @admin_required - async def create(self, *args, **kwargs): - return super().create(*args, **kwargs) + self.filter = admin_required(self.filter) - async def read(self, *args, **kwargs): - return super().read(*args, **kwargs) - @admin_required - async def update(self, *args, **kwargs): - return super().update(*args, **kwargs) - - @admin_required - async def delete(self, *args, **kwargs): - return super().delete(*args, **kwargs) - - @admin_required - async def create_update(self, *args, **kwargs): - return super().create_update(*args, **kwargs) - - async def query(self, *args, **kwargs): - return super().query(*args, **kwargs) + super().__init__(entity, table, schema) diff --git a/src/biodm/components/controllers/controller.py b/src/biodm/components/controllers/controller.py index 7c1592f..04ca5d9 100644 --- a/src/biodm/components/controllers/controller.py +++ b/src/biodm/components/controllers/controller.py @@ -29,10 +29,12 @@ class Controller(ApiComponent): """ @abstractmethod def routes(self, **kwargs): + """""" raise NotImplementedError @property def schema_gen(self): + """""" return self.app.schema_generator async def openapi_schema(self, _): @@ -71,9 +73,9 @@ def deserialize(cls, data: Any) -> (Any | list | dict | None): cls.schema.unknown = EXCLUDE return cls.schema.loads(json_data=data) except ValidationError as e: - raise PayloadValidationError(e) + raise PayloadValidationError(e) from e except json.JSONDecodeError as e: - raise PayloadJSONDecodingError(e.messages) + raise PayloadJSONDecodingError(e) from e except Exception as e: raise e @@ -84,4 +86,4 @@ def serialize(cls, data: Any, many: bool) -> (str | Any): serialized = cls.schema.dump(data, many=many) return json.dumps(serialized, indent=cls.app.config.INDENT) except MissingGreenlet as e: - raise AsyncDBError(e) + raise AsyncDBError(e) from e diff --git a/src/biodm/components/controllers/resourcecontroller.py b/src/biodm/components/controllers/resourcecontroller.py index 4dfa35f..8557064 100644 --- a/src/biodm/components/controllers/resourcecontroller.py +++ b/src/biodm/components/controllers/resourcecontroller.py @@ -59,7 +59,7 @@ def __init__(self, app: Api, entity: str=None, table: Base=None, schema: Schema= def _infer_entity_name(self) -> str: """Infer entity name from controller name.""" - return self.__class__.__name__.split("Controller")[0] + return self.__class__.__name__.split('Controller', maxsplit=1)[0] @property def prefix(self) -> str: @@ -90,24 +90,24 @@ def _infer_table(self) -> Base: """Tries to import from instance module reference.""" try: return self.app.tables.__dict__[self.resource] - except: + except Exception as e: raise ValueError( f"{self.__class__.__name__} could not find {self.resource} Table." " Alternatively if you are following another naming convention " "you should provide it as 'table' arg when creating a new controller" - ) + ) from e def _infer_schema(self) -> Schema: """Tries to import from instance module reference.""" isn = f"{self.resource}Schema" try: return self.app.schemas.__dict__[isn]() - except: + except Exception as e: raise ValueError( f"{self.__class__.__name__} could not find {isn} Schema. " "Alternatively if you are following another naming convention " "you should provide it as 'schema' arg when creating a new controller" - ) + ) from e def routes(self, child_routes=None) -> Mount: """Sets up standard RESTful endpoints. diff --git a/src/biodm/components/services/dbservice.py b/src/biodm/components/services/dbservice.py index 1f6e299..5800912 100644 --- a/src/biodm/components/services/dbservice.py +++ b/src/biodm/components/services/dbservice.py @@ -260,7 +260,7 @@ async def _insert_composite( for key, sub in composite.nested.items(): attr = f"id_{key}" if hasattr(item, attr): - item.__setattr__(attr, sub.id) + setattr(item, attr, sub.id) await session.commit() # Populate many-to-one fields with delayed lists. diff --git a/src/biodm/managers/kcmanager.py b/src/biodm/managers/kcmanager.py index dc384e5..b997372 100644 --- a/src/biodm/managers/kcmanager.py +++ b/src/biodm/managers/kcmanager.py @@ -37,7 +37,7 @@ def __init__(self, app: Api): except KeycloakError as e: raise KeycloakUnavailableError( f"Failed to initialize connection to Keycloak: {e.error_message}" - ) + ) from e @property def admin(self): @@ -55,7 +55,9 @@ async def auth_url(self, redirect_uri: str): async def redeem_code_for_token(self, code: str, redirect_uri: str): """Code for token.""" - return self.openid.token(grant_type="authorization_code", code=code, redirect_uri=redirect_uri) + return self.openid.token( + grant_type="authorization_code", code=code, redirect_uri=redirect_uri + ) async def decode_token(self, token: str): """Decode token.""" @@ -91,48 +93,69 @@ async def create_user(self, data: dict, groups: List[str]=None) -> str: try: return self.admin.create_user(payload, exist_ok=True) except KeycloakError as e: - raise FailedCreate(f"Could not create Keycloak Group with data: {payload} -- msg: {e.error_message}") + raise FailedCreate( + "Could not create Keycloak Group with data: " + f"{payload} -- msg: {e.error_message}" + ) from e - async def update_user(self, id: str, data: dict): + async def update_user(self, user_id: str, data: dict): """Update user.""" try: payload = self._user_data_to_payload(data) - return self.admin.update_user(user_id=id, payload=payload) + return self.admin.update_user(user_id=user_id, payload=payload) except KeycloakError as e: - raise FailedUpdate(f"Could not update Keycloak User(id={id}) with data: {data} -- msg: {e.error_message}.") + raise FailedUpdate( + "Could not update Keycloak " + f"User(id={user_id}) with data: {data} -- msg: {e.error_message}." + ) from e - async def delete_user(self, id: str) -> None: + async def delete_user(self, user_id: str) -> None: """Delete user with this id.""" try: - self.admin.delete_user(id) + self.admin.delete_user(user_id) except KeycloakDeleteError as e: - raise FailedDelete(f"Could not delete Keycloak User(id={id}): {e.error_message}.") + raise FailedDelete( + "Could not delete Keycloak " + f"User(id={user_id}): {e.error_message}." + ) from e async def create_group(self, data: dict) -> str: """Create group.""" try: return self.admin.create_group({"name": data["name"]}) except KeycloakError as e: - raise FailedCreate(f"Could not create Keycloak Group with data: {data} -- msg: {e.error_message}") + raise FailedCreate( + "Could not create Keycloak Group with data: " + f"{data} -- msg: {e.error_message}" + ) from e - async def update_group(self, id: str, data: dict): + async def update_group(self, group_id: str, data: dict): """Update group.""" try: payload = self._group_data_to_payload(data) - return self.admin.update_group(group_id=id, payload=payload) + return self.admin.update_group(group_id=group_id, payload=payload) except KeycloakError as e: - raise FailedUpdate(f"Could not update Keycloak Group(id={id}) with data: {data} -- msg: {e.error_message}.") + raise FailedUpdate( + "Could not update Keycloak " + f"Group(id={group_id}) with data: {data} -- msg: {e.error_message}." + ) from e - async def delete_group(self, id: str): + async def delete_group(self, user_id: str): """Delete group with this id.""" try: - return self.admin.delete_group(id) + return self.admin.delete_group(user_id) except KeycloakDeleteError as e: - raise FailedDelete(f"Could not delete Keycloak Group(id={id}): {e.error_message}.") + raise FailedDelete( + "Could not delete Keycloak " + f"Group(id={user_id}): {e.error_message}." + ) from e async def group_user_add(self, user_id: str, group_id: str): """Add user with user_id to group with group_id.""" try: return self.admin.group_user_add(user_id, group_id) except KeycloakError as e: - raise FailedCreate(f"Keycloak failed adding User(id={user_id}) to Group(id={group_id}): {e.error_message}") + raise FailedCreate( + "Keycloak failed adding " + f"User(id={user_id}) to Group(id={group_id}): {e.error_message}" + ) from e