diff --git a/basemodels/manifest/data/helpers.py b/basemodels/manifest/data/helpers.py index 7514adb..de8ac2f 100644 --- a/basemodels/manifest/data/helpers.py +++ b/basemodels/manifest/data/helpers.py @@ -1,5 +1,7 @@ import requests from pydantic import ValidationError, BaseModel, HttpUrl +from pydantic.error_wrappers import ErrorWrapper + from basemodels.constants import SUPPORTED_CONTENT_TYPES @@ -13,4 +15,9 @@ def validate_content_type(uri: str) -> None: response.raise_for_status() content_type = response.headers.get("Content-Type", "") if content_type not in SUPPORTED_CONTENT_TYPES: - raise ValidationError(f"Unsupported type {content_type}", ExampleResourceModel()) + raise ValidationError( + [ + ErrorWrapper(ValueError(f"Unsupported type {content_type}"), "answer_example_uri") + ], + ExampleResourceModel + ) diff --git a/basemodels/manifest/data/requester_question_example.py b/basemodels/manifest/data/requester_question_example.py index 9787525..720a75b 100644 --- a/basemodels/manifest/data/requester_question_example.py +++ b/basemodels/manifest/data/requester_question_example.py @@ -1,4 +1,6 @@ from typing import Union + +from pydantic.error_wrappers import ErrorWrapper from requests import RequestException from pydantic import ValidationError from .helpers import validate_content_type, ExampleResourceModel @@ -18,14 +20,22 @@ def validate_requester_example_image( uri_val = uri validate_content_type(uri) else: - raise ValidationError(f"Not supported format for requester_question_example.") + raise ValueError(f"Not supported format for requester_question_example.") except RequestException as e: raise ValidationError( - f"could not retrieve requester example ({uri_val})", + [ + ErrorWrapper(ValueError(f"could not retrieve requester example ({uri_val})"), "answer_example_uri") + ], ExampleResourceModel ) from e except ValidationError as e: raise ValidationError( - f"requester example image for {uri_val} has unsupported type", + [ + ErrorWrapper( + ValueError(f"requester example image for {uri_val} has unsupported type"), + "answer_example_uri" + ) + ], ExampleResourceModel ) from e + diff --git a/basemodels/manifest/data/requester_restricted_answer_set.py b/basemodels/manifest/data/requester_restricted_answer_set.py index cd2cdb6..37e0907 100644 --- a/basemodels/manifest/data/requester_restricted_answer_set.py +++ b/basemodels/manifest/data/requester_restricted_answer_set.py @@ -1,3 +1,4 @@ +from pydantic.error_wrappers import ErrorWrapper from requests import RequestException from pydantic import ValidationError from .helpers import validate_content_type, ExampleResourceModel @@ -23,11 +24,23 @@ def validate_requester_restricted_answer_set_uris(restricted_answer_set: dict) - validate_content_type(uri) except RequestException as e: raise ValidationError( - f"could not retrieve requester restricted answer set example uri ({uri})", + [ + ErrorWrapper( + ValueError(f"could not retrieve requester restricted answer set example uri ({uri})"), + "answer_example_uri" + ) + ], ExampleResourceModel ) from e except ValidationError as e: + raise ValidationError( - f"requester restricted answer set example uri ({uri}) content type failed validation", + [ + ErrorWrapper( + ValueError(f"requester restricted answer set example uri " + f"({uri}) content type failed validation"), + "answer_example_uri" + ) + ], ExampleResourceModel ) from e diff --git a/basemodels/manifest/data/taskdata.py b/basemodels/manifest/data/taskdata.py index d3c5ee7..5c77bc0 100644 --- a/basemodels/manifest/data/taskdata.py +++ b/basemodels/manifest/data/taskdata.py @@ -3,6 +3,7 @@ import requests from pydantic import BaseModel, HttpUrl, validate_model, ValidationError, validator, root_validator +from pydantic.error_wrappers import ErrorWrapper from requests import RequestException from basemodels.constants import SUPPORTED_CONTENT_TYPES @@ -77,13 +78,23 @@ def validate_content_type(uri: str) -> None: response = requests.head(uri) response.raise_for_status() except RequestException as e: - raise ValidationError(f"taskdata content type ({uri}) validation failed", TaskDataEntry()) from e + raise ValidationError( + [ + ErrorWrapper(ValueError(f"taskdata content type ({uri}) validation failed"), "datapoint_uri") + ], + TaskDataEntry + ) from e content_type = response.headers.get("Content-Type", "") if content_type not in SUPPORTED_CONTENT_TYPES: raise ValidationError( - f"taskdata entry datapoint_uri has unsupported type {content_type}", - TaskDataEntry(), + [ + ErrorWrapper( + ValueError(f"taskdata entry datapoint_uri has unsupported type {content_type}"), + "datapoint_uri" + ) + ], + TaskDataEntry ) diff --git a/basemodels/manifest/manifest.py b/basemodels/manifest/manifest.py index b885077..dcf2ebe 100644 --- a/basemodels/manifest/manifest.py +++ b/basemodels/manifest/manifest.py @@ -11,6 +11,7 @@ from .data.requester_restricted_answer_set import validate_requester_restricted_answer_set_uris from .data.taskdata import validate_taskdata_entry from pydantic import BaseModel, validator, ValidationError, validate_model, HttpUrl, AnyHttpUrl, root_validator +from pydantic.error_wrappers import ErrorWrapper from pydantic.fields import Field from decimal import Decimal from basemodels.manifest.restricted_audience import RestrictedAudience @@ -116,7 +117,7 @@ class TaskData(BaseModel): @validator("datapoint_uri", always=True) def validate_datapoint_uri(cls, value): if value and len(value) < 10: - raise ValidationError("datapoint_uri need to be at least 10 char length.") + raise ValueError("datapoint_uri need to be at least 10 char length.") return value @root_validator @@ -188,18 +189,18 @@ def validate_requester_restricted_answer_set(cls, value, values, **kwargs): # validation runs before other params, so need to handle missing case if "request_type" not in values: - raise ValidationError("request_type missing") + raise ValueError("request_type missing") if values["request_type"] == BaseJobTypesEnum.image_label_area_select: if not value or len(value.keys()) == 0: value = {"label": {}} values["requester_restricted_answer_set"] = value if values["request_type"] == BaseJobTypesEnum.image_label_multiple_choice: if not value or len(value.keys()) <= 1: - raise ValidationError( + raise ValueError( "image_label_multiple_choice needs at least 2+ options in requester_restricted_answer_set" ) elif len(value.keys()) > 4: - raise ValidationError( + raise ValueError( "image_label_multiple_choice can not handle more than 4 options requester_restricted_answer_set" ) return value @@ -214,13 +215,13 @@ def validate_requester_restricted_answer_set(cls, value, values, **kwargs): def validate_requester_question_example(cls, value, values, **kwargs): # validation runs before other params, so need to handle missing case if not ("request_type" in values): - raise ValidationError("request_type missing") + raise ValueError("request_type missing") # based on https://github.com/hCaptcha/hmt-basemodels/issues/27#issuecomment-590706643 supports_lists = [BaseJobTypesEnum.image_label_area_select, BaseJobTypesEnum.image_label_binary] if isinstance(value, list) and not values["request_type"] in supports_lists: - raise ValidationError("Lists are not allowed in this challenge type") + raise ValueError("Lists are not allowed in this challenge type") return value unsafe_content: bool = False @@ -235,7 +236,7 @@ def validate_requester_question_example(cls, value, values, **kwargs): @validator("groundtruth", always=True) def validate_groundtruth(cls, v, values, **kwargs): if "groundtruth_uri" in values and "groundtruth" in values: - raise ValidationError("Specify only groundtruth_uri or groundtruth, not both.") + raise ValueError("Specify only groundtruth_uri or groundtruth, not both.") return v # Configuration id -- XXX LEGACY @@ -348,11 +349,11 @@ def validate_requester_restricted_answer_set(cls, value, values, **kwargs): if values["request_type"] == BaseJobTypesEnum.image_label_multiple_choice: if not value or len(value.keys()) <= 1: - raise ValidationError( + raise ValueError( "image_label_multiple_choice needs at least 2+ options in requester_restricted_answer_set" ) elif len(value.keys()) > 4: - raise ValidationError( + raise ValueError( "image_label_multiple_choice can not handle more than 4 options requester_restricted_answer_set" ) return value @@ -417,10 +418,20 @@ def validate_groundtruth_uri(manifest: dict): validate_image_content_type = False except (ValidationError, RequestException) as e: - raise ValidationError(f"{uri_key} validation failed: {e}", Manifest) from e + raise ValidationError( + [ + ErrorWrapper(ValueError(f"Validation failed for {uri}: {e}"), uri_key) + ], + Manifest + ) from e if entries_count == 0: - raise ValidationError(f"fetched {uri_key} is empty", Manifest) + raise ValidationError( + [ + ErrorWrapper(ValueError(f"fetched {uri} is empty"), uri_key) + ], + Manifest + ) def validate_taskdata_uri(manifest: dict): @@ -446,10 +457,20 @@ def validate_taskdata_uri(manifest: dict): validate_image_content_type = False # We want to validate only first entry for content type except (ValidationError, RequestException) as e: - raise ValidationError(f"{uri_key} validation failed: {e}", Manifest) from e + raise ValidationError( + [ + ErrorWrapper(ValueError(f"Validation failed for {uri}: {e}"), uri_key) + ], + Manifest + ) from e if entries_count == 0: - raise ValidationError(f"fetched {uri_key} is empty", Manifest) + raise ValidationError( + [ + ErrorWrapper(ValueError(f"fetched {uri} is empty"), uri_key) + ], + Manifest + ) def validate_manifest_example_images(manifest: dict): diff --git a/pyproject.toml b/pyproject.toml index 0464607..d4eddb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "hmt-basemodels" -version = "0.2.2" +version = "0.2.3" description = "" authors = ["Intuition Machines, Inc "] packages = [ diff --git a/setup.py b/setup.py index 58f83e9..6da955f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setuptools.setup( name="hmt-basemodels", - version="0.2.2", + version="0.2.3", author="HUMAN Protocol", description="Common data models shared by various components of the Human Protocol stack", url="https://github.com/hCaptcha/hmt-basemodels",