Skip to content

Commit

Permalink
feat: add centralized error handling
Browse files Browse the repository at this point in the history
Added centralized error handling facility, along with common base error
class. Additionally, fixed a number of typing errors to get tests to
pass.
  • Loading branch information
hbooth committed Oct 1, 2024
1 parent 297ac83 commit 5185d5c
Show file tree
Hide file tree
Showing 50 changed files with 924 additions and 1,471 deletions.
406 changes: 355 additions & 51 deletions src/dioptra/restapi/errors.py

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/dioptra/restapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import datetime
import functools
from collections import Counter
from importlib.resources import as_file, files
from typing import Any, Callable, List, Protocol, Type, cast

Expand Down Expand Up @@ -325,3 +326,26 @@ def setup_injection(api: Api, injector: Injector) -> None:
ma.URL: str,
ma.UUID: str,
}


# Validation Functions
def find_non_unique(name: str, parameters: list[dict[str, Any]]) -> list[str]:
"""
Finds all values of a key that are not unique in a list of dictionaries.
Useful for checking that a provided input satisfies uniqueness constraints.
Note that the key name must be in every dictionary of the provided list.
Args:
name: the name of the parameter to check
parameters: the input parameters to check
Returns:
A list of all values that were provided more than once, or an empty list if all
values of the key were unique
"""
name_count: Counter = Counter()
# this line fails if a parameter is missing a "name" value
name_count.update([parameter[name] for parameter in parameters])
# create a list of all name values that appear more than once
return [key for key in name_count.keys() if name_count[key] > 1]
3 changes: 0 additions & 3 deletions src/dioptra/restapi/v1/artifacts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,3 @@
#
# ACCESS THE FULL CC BY 4.0 LICENSE HERE:
# https://creativecommons.org/licenses/by/4.0/legalcode
from . import errors

__all__ = ["errors"]
40 changes: 18 additions & 22 deletions src/dioptra/restapi/v1/artifacts/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,17 @@
from structlog.stdlib import BoundLogger

from dioptra.restapi.db import db, models
from dioptra.restapi.errors import BackendDatabaseError
from dioptra.restapi.errors import (
BackendDatabaseError,
EntityDoesNotExistError,
EntityExistsError,
SortParameterValidationError,
)
from dioptra.restapi.v1 import utils
from dioptra.restapi.v1.groups.service import GroupIdService
from dioptra.restapi.v1.jobs.service import ExperimentJobIdService, JobIdService
from dioptra.restapi.v1.shared.search_parser import construct_sql_query_filters

from .errors import (
ArtifactAlreadyExistsError,
ArtifactDoesNotExistError,
ArtifactSortError,
)

LOGGER: BoundLogger = structlog.stdlib.get_logger()

RESOURCE_TYPE: Final[str] = "artifact"
Expand Down Expand Up @@ -97,14 +96,14 @@ def create(
The newly created artifact object.
Raises:
ArtifactAlreadyExistsError: If the artifact already exists.
EntityExistsError: If the artifact already exists.
"""
log: BoundLogger = kwargs.get("log", LOGGER.new())

if self._artifact_uri_service.get(uri, log=log) is not None:
log.debug("Artifact uri already exists", uri=uri)
raise ArtifactAlreadyExistsError
duplicate = self._artifact_uri_service.get(uri, log=log)
if duplicate is not None:
raise EntityExistsError(RESOURCE_TYPE, duplicate.resource_id, uri=uri)

job_dict = cast(
utils.JobDict,
Expand Down Expand Up @@ -220,8 +219,7 @@ def get(
sort_column = sort_column.asc()
latest_artifacts_stmt = latest_artifacts_stmt.order_by(sort_column)
elif sort_by_string and sort_by_string not in SORTABLE_FIELDS:
log.debug(f"sort_by_string: '{sort_by_string}' is not in SORTABLE_FIELDS")
raise ArtifactSortError
raise SortParameterValidationError(RESOURCE_TYPE, sort_by_string)

artifacts = db.session.scalars(latest_artifacts_stmt).all()

Expand Down Expand Up @@ -310,7 +308,7 @@ def get(
artifact_uri: str,
error_if_not_found: bool = False,
**kwargs,
) -> utils.ArtifactDict | None:
) -> models.Artifact | None:
"""Fetch an artifact by its unique uri.
Args:
Expand All @@ -323,7 +321,7 @@ def get(
The artifact object if found, otherwise None.
Raises:
ArtifactDoesNotExistError: If the artifact is not found and
EntityDoesNotExistError: If the artifact is not found and
`error_if_not_found` is True.
"""
Expand All @@ -345,8 +343,7 @@ def get(

if artifact is None:
if error_if_not_found:
log.debug("Artifact not found", artifact_uri=artifact_uri)
raise ArtifactDoesNotExistError
raise EntityDoesNotExistError(RESOURCE_TYPE, artifact_uri=artifact_uri)

return None

Expand All @@ -373,7 +370,7 @@ def get(
The artifact object if found, otherwise None.
Raises:
ArtifactDoesNotExistError: If the artifact is not found and
EntityDoesNotExistError: If the artifact is not found and
`error_if_not_found` is True.
"""
log: BoundLogger = kwargs.get("log", LOGGER.new())
Expand All @@ -393,8 +390,7 @@ def get(

if artifact is None:
if error_if_not_found:
log.debug("Artifact not found", artifact_id=artifact_id)
raise ArtifactDoesNotExistError
raise EntityDoesNotExistError(RESOURCE_TYPE, artifact_id=artifact_id)

return None

Expand Down Expand Up @@ -433,9 +429,9 @@ def modify(
The updated artifact object.
Raises:
ArtifactDoesNotExistError: If the artifact is not found and
EntityDoesNotExistError: If the artifact is not found and
`error_if_not_found` is True.
ArtifactAlreadyExistsError: If the artifact name already exists.
EntityExistsError: If the artifact name already exists.
"""
log: BoundLogger = kwargs.get("log", LOGGER.new())

Expand Down
3 changes: 0 additions & 3 deletions src/dioptra/restapi/v1/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,3 @@
#
# ACCESS THE FULL CC BY 4.0 LICENSE HERE:
# https://creativecommons.org/licenses/by/4.0/legalcode
from . import errors

__all__ = ["errors"]
77 changes: 0 additions & 77 deletions src/dioptra/restapi/v1/entrypoints/errors.py

This file was deleted.

Loading

0 comments on commit 5185d5c

Please sign in to comment.