From 6d66e77da8e2ee619f19e249182c0c03e7c1b29b Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Simonard <pierre.olivier.simonard@gmail.com> Date: Mon, 9 Oct 2023 15:23:19 +0200 Subject: [PATCH] ENH - Better handling of pydantic error msgs (#546) * better handling of pydantic error msgs * Better handling of spec validation errors * Fix - forgotten return * pydantic errors : custom exception * linting --- .../conda_store_server/schema.py | 40 ++++++++++++++++++- .../conda_store_server/server/views/api.py | 2 + 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/conda-store-server/conda_store_server/schema.py b/conda-store-server/conda_store_server/schema.py index 11dd685cb..173174249 100644 --- a/conda-store-server/conda_store_server/schema.py +++ b/conda-store-server/conda_store_server/schema.py @@ -6,8 +6,8 @@ import sys from typing import Any, Callable, Dict, List, Optional, Union -from conda_store_server import conda_utils -from pydantic import BaseModel, Field, constr, validator +from conda_store_server import conda_utils, utils +from pydantic import BaseModel, Field, ValidationError, constr, validator def _datetime_factory(offset: datetime.timedelta): @@ -406,6 +406,42 @@ def check_dependencies(cls, v): return v + @classmethod + def parse_obj(cls, specification): + try: + return super().parse_obj(specification) + except ValidationError as e: + # there can be multiple errors. Let's build a comprehensive summary + # to return to the end user. + + # hr stands for "human readable" + all_errors_hr = [] + + for err in e.errors(): + error_type = err["type"] + error_loc = err["loc"] + + # fallback case : if we can't figure out the error, let's build a default + # one based on the data returned by Pydantic. + human_readable_error = ( + f"{err['msg']} (type={error_type}, loc={error_loc})" + ) + + if error_type == "type_error.none.not_allowed": + if error_loc[0] == "name": + human_readable_error = ( + "The name of the environment cannot be empty." + ) + else: + if len(error_loc) == 1: + human_readable_error = f"Invalid YAML : A forbidden `None` value has been encountered in section {error_loc[0]}" + elif len(error_loc) == 2: + human_readable_error = f"Invalid YAML : A forbidden `None` value has been encountered in section `{error_loc[0]}`, line {error_loc[1]}" + + all_errors_hr.append(human_readable_error) + + raise utils.CondaStoreError(all_errors_hr) + ############################### # Docker Registry Schema diff --git a/conda-store-server/conda_store_server/server/views/api.py b/conda-store-server/conda_store_server/server/views/api.py index 2661c49e7..3e53097eb 100644 --- a/conda-store-server/conda_store_server/server/views/api.py +++ b/conda-store-server/conda_store_server/server/views/api.py @@ -560,6 +560,8 @@ async def api_post_specification( specification = schema.CondaSpecification.parse_obj(specification) except yaml.error.YAMLError: raise HTTPException(status_code=400, detail="Unable to parse. Invalid YAML") + except utils.CondaStoreError as e: + raise HTTPException(status_code=400, detail="\n".join(e.args[0])) except pydantic.ValidationError as e: raise HTTPException(status_code=400, detail=str(e))