From c375d8e9d4c4fb5d411c54ebf69cadd477f027fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20F=C3=B6rster?= Date: Wed, 5 Mar 2025 14:35:29 +0100 Subject: [PATCH] adding result objects, renaming components --- src/delta_barth/analysis/forecast.py | 9 +++-- src/delta_barth/api/common.py | 13 +------ src/delta_barth/constants.py | 1 + src/delta_barth/errors.py | 51 ++++++++++++++++++---------- src/delta_barth/types.py | 28 ++++++++++----- tests/analysis/test_forecast.py | 12 +++---- tests/test_errors.py | 16 ++++----- 7 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/delta_barth/analysis/forecast.py b/src/delta_barth/analysis/forecast.py index 71ea51a..d964c4e 100644 --- a/src/delta_barth/analysis/forecast.py +++ b/src/delta_barth/analysis/forecast.py @@ -11,11 +11,10 @@ from xgboost import XGBRegressor from delta_barth._management import ERROR_HANDLER from delta_barth.analysis import parse from delta_barth.constants import COL_MAP_SALES_PROGNOSIS, FEATURES_SALES_PROGNOSIS -from delta_barth.types import CustomerDataSalesForecast, DataPipelineErrors, doptResult +from delta_barth.types import CustomerDataSalesForecast, DataPipeStates, PipeResult if TYPE_CHECKING: from delta_barth.api.common import SalesPrognosisResponse - from delta_barth.types import FcResult # TODO check pandera for DataFrame validation @@ -66,7 +65,7 @@ def sales_per_customer( data: pd.DataFrame, customer_id: int, min_num_data_points: int = 100, -) -> doptResult: +) -> PipeResult: """_summary_ Parameters @@ -106,7 +105,7 @@ def sales_per_customer( # check data availability if len(df_cust) < min_num_data_points: - return doptResult(resp=ERROR_HANDLER.data_pipelines.TOO_FEW_POINTS, res=None) + return PipeResult(status=ERROR_HANDLER.pipe_states.TOO_FEW_POINTS, data=None) else: # Entwicklung der Umsätze: definierte Zeiträume Monat df_cust["year"] = df_cust["date"].dt.year @@ -145,4 +144,4 @@ def sales_per_customer( test = test.reset_index(drop=True) # umsetzung, prognose - return doptResult(resp=ERROR_HANDLER.data_pipelines.SUCCESS, res=test) + return PipeResult(status=ERROR_HANDLER.pipe_states.SUCCESS, data=test) diff --git a/src/delta_barth/api/common.py b/src/delta_barth/api/common.py index dbb5b06..f74e02c 100644 --- a/src/delta_barth/api/common.py +++ b/src/delta_barth/api/common.py @@ -14,22 +14,11 @@ from delta_barth.errors import ( UnknownApiErrorCode, UnspecifiedRequestType, ) -from delta_barth.types import HttpRequestTypes +from delta_barth.types import DelBarApiError, HttpRequestTypes LOGIN_ERROR_CODES_KNOWN: Final[frozenset[int]] = frozenset((400, 401, 409, 500)) -class DelBarApiError(BaseModel): - status_code: int - message: str = "" - code: str | None = None - hints: str | None = None - type: str | None = None - title: str | None = None - errors: dict | None = None - traceId: str | None = None - - def _raise_for_unknown_error( resp: Response, ) -> Never: diff --git a/src/delta_barth/constants.py b/src/delta_barth/constants.py index 408bcce..91c8bec 100644 --- a/src/delta_barth/constants.py +++ b/src/delta_barth/constants.py @@ -5,6 +5,7 @@ from delta_barth.types import CurrentConnection, HttpContentHeaders # ** error handling DEFAULT_INTERNAL_ERR_CODE: Final[int] = 100 +DEFAULT_API_ERR_CODE: Final[int] = 400 HTTP_BASE_CONTENT_HEADERS: Final[HttpContentHeaders] = { "Content-type": "application/json", diff --git a/src/delta_barth/errors.py b/src/delta_barth/errors.py index ba59fef..4929b77 100644 --- a/src/delta_barth/errors.py +++ b/src/delta_barth/errors.py @@ -2,11 +2,11 @@ from __future__ import annotations from typing import TYPE_CHECKING, Final -from delta_barth.constants import DEFAULT_INTERNAL_ERR_CODE -from delta_barth.types import DataPipelineErrors, doptResponse +from delta_barth.constants import DEFAULT_API_ERR_CODE, DEFAULT_INTERNAL_ERR_CODE +from delta_barth.types import DataPipeStates, Status if TYPE_CHECKING: - from delta_barth.types import ErrorDescription + from delta_barth.types import DelBarApiError, ErrorDescription class UnspecifiedRequestType(Exception): @@ -26,7 +26,7 @@ class FeaturesMissingError(Exception): ## ** internal error handling -DATA_PIPELINE_ERRORS_DESCR: Final[tuple[ErrorDescription, ...]] = ( +DATA_PIPELINE_STATUS_DESCR: Final[tuple[ErrorDescription, ...]] = ( ("SUCCESS", 0, "Erfolg"), ("TOO_FEW_POINTS", 1, "Datensatz besitzt nicht genügend Datenpunkte"), ("BAD_QUALITY", 2, "Prognosequalität des Modells unzureichend"), @@ -35,33 +35,48 @@ DATA_PIPELINE_ERRORS_DESCR: Final[tuple[ErrorDescription, ...]] = ( class ErrorHandler: def __init__(self) -> None: - self._data_pipelines: DataPipelineErrors | None = None - self._parse_data_pipeline_errors() + self._pipe_states: DataPipeStates | None = None + self._parse_data_pipe_states() @property - def data_pipelines(self) -> DataPipelineErrors: - assert self._data_pipelines is not None, ( + def pipe_states(self) -> DataPipeStates: + assert self._pipe_states is not None, ( "tried to access not parsed data pipeline errors" ) - return self._data_pipelines + return self._pipe_states - def _parse_data_pipeline_errors(self) -> None: - if self._data_pipelines is not None: + def _parse_data_pipe_states(self) -> None: + if self._pipe_states is not None: return - parsed_errors: dict[str, doptResponse] = {} - for err in DATA_PIPELINE_ERRORS_DESCR: - parsed_errors[err[0]] = doptResponse(status_code=err[1], description=err[2]) + parsed_errors: dict[str, Status] = {} + for err in DATA_PIPELINE_STATUS_DESCR: + parsed_errors[err[0]] = Status(status_code=err[1], description=err[2]) - self._data_pipelines = DataPipelineErrors(**parsed_errors) + self._pipe_states = DataPipeStates(**parsed_errors) - def internal_error( + def error( self, description: str, message: str = "", err_code: int = DEFAULT_INTERNAL_ERR_CODE, - ) -> doptResponse: - return doptResponse( + ) -> Status: + return Status( status_code=err_code, description=description, message=message, ) + + def api_error( + self, + error: DelBarApiError, + ) -> Status: + description = "Es ist ein Fehler bei der Kommunikation mit dem API-Server aufgetreten" + message = ( + "Bitte beachten Sie die zusätzliche Fehlerausgabe des Servers in dieser Antwort" + ) + return Status( + status_code=DEFAULT_API_ERR_CODE, + description=description, + message=message, + api_server_error=error, + ) diff --git a/src/delta_barth/types.py b/src/delta_barth/types.py index 96dafea..5930183 100644 --- a/src/delta_barth/types.py +++ b/src/delta_barth/types.py @@ -12,17 +12,29 @@ from pydantic import BaseModel, SkipValidation ErrorDescription: TypeAlias = tuple[str, int, str] -class doptResponse(BaseModel): +class Status(BaseModel): status_code: SkipValidation[int] description: SkipValidation[str] message: SkipValidation[str] = "" + api_server_error: SkipValidation[DelBarApiError | None] = None + + +class DelBarApiError(BaseModel): + status_code: int + message: str = "" + code: str | None = None + hints: str | None = None + type: str | None = None + title: str | None = None + errors: dict | None = None + traceId: str | None = None @dataclass(slots=True) -class DataPipelineErrors: - SUCCESS: doptResponse - TOO_FEW_POINTS: doptResponse - BAD_QUALITY: doptResponse +class DataPipeStates: + SUCCESS: Status + TOO_FEW_POINTS: Status + BAD_QUALITY: Status class HttpRequestTypes(enum.StrEnum): @@ -92,6 +104,6 @@ class CustomerDataSalesForecast: @dataclass(slots=True) -class doptResult: - resp: doptResponse - res: pd.DataFrame | None +class PipeResult: + status: Status + data: pd.DataFrame | None diff --git a/tests/analysis/test_forecast.py b/tests/analysis/test_forecast.py index 7d558ae..7fda954 100644 --- a/tests/analysis/test_forecast.py +++ b/tests/analysis/test_forecast.py @@ -5,18 +5,18 @@ from delta_barth.analysis import forecast as fc def test_sales_per_customer_success(sales_data): customer_id = 1133 - err, res = fc.sales_per_customer(sales_data, customer_id) + res = fc.sales_per_customer(sales_data, customer_id) - assert err == 0 - assert res is not None + assert res.status.status_code == 0 + assert res.data is not None def test_sales_per_customer_too_few_data_points(sales_data): customer_id = 1000 - err, res = fc.sales_per_customer(sales_data, customer_id) + res = fc.sales_per_customer(sales_data, customer_id) - assert err == 1 - assert res is None + assert res.status.status_code == 1 + assert res.data is None def test_parse_api_resp_to_df(exmpl_api_sales_prognosis_resp): diff --git a/tests/test_errors.py b/tests/test_errors.py index f33b104..995cdf8 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -5,25 +5,25 @@ from typing import cast import delta_barth._management from delta_barth import errors -from delta_barth.types import doptResponse +from delta_barth.types import Status def test_error_handler_parsing(): - predef_errs = errors.DATA_PIPELINE_ERRORS_DESCR + predef_errs = errors.DATA_PIPELINE_STATUS_DESCR err_hdlr = delta_barth._management.ErrorHandler() - assert err_hdlr.data_pipelines is not None - parsed_pipe_errs = err_hdlr.data_pipelines + assert err_hdlr.pipe_states is not None + parsed_pipe_errs = err_hdlr.pipe_states parsed_pipe_errs = asdict(parsed_pipe_errs) for err in predef_errs: - dopt_err = cast(doptResponse, parsed_pipe_errs[err[0]]) - assert isinstance(dopt_err, doptResponse) + dopt_err = cast(Status, parsed_pipe_errs[err[0]]) + assert isinstance(dopt_err, Status) assert dopt_err.status_code == err[1] assert dopt_err.description == err[2] assert dopt_err.message == "" - err_hdlr._parse_data_pipeline_errors() + err_hdlr._parse_data_pipe_states() def test_error_handler_internal(): @@ -32,7 +32,7 @@ def test_error_handler_internal(): ERR_CODE = 101 err_hdlr = delta_barth._management.ErrorHandler() - new_err = err_hdlr.internal_error( + new_err = err_hdlr.error( description=DESCRIPTION, message=MESSAGE, err_code=ERR_CODE,