added handling for API connectivity errors
This commit is contained in:
parent
b93b070682
commit
5d78fc9e02
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "delta-barth"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
description = "workflows and pipelines for the Python-based Plugin of Delta Barth's ERP system"
|
||||
authors = [
|
||||
{name = "Florian Förster", email = "f.foerster@d-opt.com"},
|
||||
@ -73,7 +73,7 @@ directory = "reports/coverage"
|
||||
|
||||
|
||||
[tool.bumpversion]
|
||||
current_version = "0.5.3"
|
||||
current_version = "0.5.4"
|
||||
parse = """(?x)
|
||||
(?P<major>0|[1-9]\\d*)\\.
|
||||
(?P<minor>0|[1-9]\\d*)\\.
|
||||
|
||||
@ -7,6 +7,7 @@ import requests
|
||||
from dopt_basics.io import combine_route
|
||||
from pydantic import BaseModel, PositiveInt, SkipValidation
|
||||
|
||||
from delta_barth.constants import API_CON_TIMEOUT
|
||||
from delta_barth.errors import STATUS_HANDLER
|
||||
from delta_barth.types import DelBarApiError, ExportResponse, ResponseType, Status
|
||||
|
||||
@ -55,7 +56,7 @@ def get_sales_prognosis_data(
|
||||
company_id: int | None = None,
|
||||
start_date: Datetime | None = None,
|
||||
) -> tuple[SalesPrognosisResponse, Status]:
|
||||
resp, status = session.assert_login()
|
||||
_, status = session.assert_login()
|
||||
if status != STATUS_HANDLER.SUCCESS:
|
||||
response = SalesPrognosisResponse(daten=tuple())
|
||||
return response, status
|
||||
@ -67,11 +68,18 @@ def get_sales_prognosis_data(
|
||||
FirmaId=company_id,
|
||||
BuchungsDatum=start_date,
|
||||
)
|
||||
resp = requests.get(
|
||||
URL,
|
||||
params=sales_prog_req.model_dump(mode="json", exclude_none=True),
|
||||
headers=session.headers, # type: ignore[argumentType]
|
||||
)
|
||||
empty_response = SalesPrognosisResponse(daten=tuple())
|
||||
try:
|
||||
resp = requests.get(
|
||||
URL,
|
||||
params=sales_prog_req.model_dump(mode="json", exclude_none=True),
|
||||
headers=session.headers, # type: ignore[argumentType]
|
||||
timeout=API_CON_TIMEOUT,
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
||||
except requests.exceptions.RequestException:
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_ERROR
|
||||
|
||||
response: SalesPrognosisResponse
|
||||
status: Status
|
||||
@ -79,7 +87,7 @@ def get_sales_prognosis_data(
|
||||
response = SalesPrognosisResponse(**resp.json())
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
else:
|
||||
response = SalesPrognosisResponse(daten=tuple())
|
||||
response = empty_response
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
|
||||
@ -38,6 +38,8 @@ class KnownDelBarApiErrorCodes(enum.Enum):
|
||||
COMMON = frozenset((400, 401, 409, 500))
|
||||
|
||||
|
||||
# ** API
|
||||
API_CON_TIMEOUT: Final[float] = 1.0 # secs to response
|
||||
# ** API response parsing
|
||||
# ** column mapping [API-Response --> Target-Features]
|
||||
COL_MAP_SALES_PROGNOSIS: Final[DualDict[str, str]] = DualDict(
|
||||
|
||||
@ -53,9 +53,19 @@ class UApiError(Exception):
|
||||
## ** internal error handling
|
||||
DATA_PIPELINE_STATUS_DESCR: Final[tuple[StatusDescription, ...]] = (
|
||||
("SUCCESS", 0, "Erfolg"),
|
||||
("TOO_FEW_POINTS", 1, "Datensatz besitzt nicht genügend Datenpunkte"),
|
||||
("TOO_FEW_MONTH_POINTS", 2, "nach Aggregation pro Monat nicht genügend Datenpunkte"),
|
||||
("NO_RELIABLE_FORECAST", 3, "Prognosequalität des Modells unzureichend"),
|
||||
(
|
||||
"CONNECTION_TIMEOUT",
|
||||
1,
|
||||
"Der Verbindungsaufbau zum API-Server dauerte zu lange. Ist der Server erreichbar?",
|
||||
),
|
||||
(
|
||||
"CONNECTION_ERROR",
|
||||
2,
|
||||
"Es ist keine Verbindung zum API-Server möglich. Ist der Server erreichbar?",
|
||||
),
|
||||
("TOO_FEW_POINTS", 3, "Datensatz besitzt nicht genügend Datenpunkte"),
|
||||
("TOO_FEW_MONTH_POINTS", 4, "nach Aggregation pro Monat nicht genügend Datenpunkte"),
|
||||
("NO_RELIABLE_FORECAST", 5, "Prognosequalität des Modells unzureichend"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ from delta_barth.api.common import (
|
||||
LoginResponse,
|
||||
validate_credentials,
|
||||
)
|
||||
from delta_barth.constants import DB_ECHO
|
||||
from delta_barth.constants import API_CON_TIMEOUT, DB_ECHO
|
||||
from delta_barth.errors import STATUS_HANDLER
|
||||
from delta_barth.logging import logger_session as logger
|
||||
from delta_barth.types import DelBarApiError, Status
|
||||
@ -191,11 +191,18 @@ class Session:
|
||||
databaseName=self.creds.database,
|
||||
mandantName=self.creds.mandant,
|
||||
)
|
||||
resp = requests.put(
|
||||
URL,
|
||||
login_req.model_dump_json(),
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
empty_response = LoginResponse(token="")
|
||||
try:
|
||||
resp = requests.put(
|
||||
URL,
|
||||
login_req.model_dump_json(),
|
||||
headers=self.headers, # type: ignore
|
||||
timeout=API_CON_TIMEOUT,
|
||||
)
|
||||
except requests.exceptions.Timeout: # pragma: no cover
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
||||
except requests.exceptions.RequestException: # pragma: no cover
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_ERROR
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
@ -204,7 +211,7 @@ class Session:
|
||||
status = STATUS_HANDLER.pipe_states.SUCCESS
|
||||
self._add_session_token(response.token)
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
response = empty_response
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
@ -216,12 +223,17 @@ class Session:
|
||||
ROUTE: Final[str] = "user/logout"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
|
||||
resp = requests.put(
|
||||
URL,
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
try:
|
||||
resp = requests.put(
|
||||
URL,
|
||||
headers=self.headers, # type: ignore
|
||||
timeout=API_CON_TIMEOUT,
|
||||
)
|
||||
except requests.exceptions.Timeout: # pragma: no cover
|
||||
return None, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
||||
except requests.exceptions.RequestException: # pragma: no cover
|
||||
return None, STATUS_HANDLER.pipe_states.CONNECTION_ERROR
|
||||
|
||||
response = None
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
@ -230,7 +242,7 @@ class Session:
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
return None, status
|
||||
|
||||
def assert_login(
|
||||
self,
|
||||
@ -246,11 +258,18 @@ class Session:
|
||||
ROUTE: Final[str] = "verkauf/umsatzprognosedaten"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
params: dict[str, int] = {"FirmaId": 999999}
|
||||
resp = requests.get(
|
||||
URL,
|
||||
params=params,
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
empty_response = LoginResponse(token="")
|
||||
try:
|
||||
resp = requests.get(
|
||||
URL,
|
||||
params=params,
|
||||
headers=self.headers, # type: ignore
|
||||
timeout=API_CON_TIMEOUT,
|
||||
)
|
||||
except requests.exceptions.Timeout: # pragma: no cover
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_TIMEOUT
|
||||
except requests.exceptions.RequestException: # pragma: no cover
|
||||
return empty_response, STATUS_HANDLER.pipe_states.CONNECTION_ERROR
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
@ -261,7 +280,7 @@ class Session:
|
||||
self._remove_session_token()
|
||||
response, status = self.login()
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
response = empty_response
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
|
||||
@ -47,6 +47,8 @@ class ExportResponse(BaseModel):
|
||||
@dataclass(slots=True)
|
||||
class DataPipeStates:
|
||||
SUCCESS: Status
|
||||
CONNECTION_TIMEOUT: Status
|
||||
CONNECTION_ERROR: Status
|
||||
TOO_FEW_POINTS: Status
|
||||
TOO_FEW_MONTH_POINTS: Status
|
||||
NO_RELIABLE_FORECAST: Status
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
from datetime import datetime as Datetime
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from delta_barth.api import requests as requests_
|
||||
from delta_barth.api.common import LoginResponse
|
||||
|
||||
|
||||
@pytest.mark.api_con_required
|
||||
@ -94,3 +96,31 @@ def test_get_sales_prognosis_data_FailApiServer(session, mock_get):
|
||||
assert status.api_server_error.message == json["message"]
|
||||
assert status.api_server_error.code == json["code"]
|
||||
assert status.api_server_error.hints == json["hints"]
|
||||
|
||||
|
||||
def test_get_sales_prognosis_data_FailGetTimeout(session, mock_get):
|
||||
mock_get.side_effect = requests.exceptions.Timeout("Test timeout")
|
||||
|
||||
def assert_login():
|
||||
return LoginResponse(token=""), requests_.STATUS_HANDLER.SUCCESS
|
||||
|
||||
session.assert_login = assert_login
|
||||
|
||||
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
||||
assert resp is not None
|
||||
assert len(resp.daten) == 0
|
||||
assert status.code == 1
|
||||
|
||||
|
||||
def test_get_sales_prognosis_data_FailGetRequestException(session, mock_get):
|
||||
mock_get.side_effect = requests.exceptions.RequestException("Test not timeout")
|
||||
|
||||
def assert_login():
|
||||
return LoginResponse(token=""), requests_.STATUS_HANDLER.SUCCESS
|
||||
|
||||
session.assert_login = assert_login
|
||||
|
||||
resp, status = requests_.get_sales_prognosis_data(session, None, None)
|
||||
assert resp is not None
|
||||
assert len(resp.daten) == 0
|
||||
assert status.code == 2
|
||||
|
||||
@ -95,7 +95,7 @@ def mock_put():
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_get():
|
||||
with patch("requests.get") as mock:
|
||||
yield mock
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user