refactoring session management
This commit is contained in:
@@ -39,7 +39,7 @@ from delta_barth.types import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from delta_barth.api.common import Session
|
||||
from delta_barth.session import Session
|
||||
from delta_barth.types import Status
|
||||
|
||||
ForecastPipe: TypeAlias = PipeResult[SalesPrognosisResultsExport, SalesForecastStatistics]
|
||||
|
||||
@@ -1,236 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Final
|
||||
from typing import Final
|
||||
|
||||
import requests
|
||||
from dopt_basics.io import combine_route
|
||||
from pydantic import BaseModel
|
||||
from requests import Response
|
||||
|
||||
import delta_barth.logging
|
||||
from delta_barth.errors import (
|
||||
STATUS_HANDLER,
|
||||
UnspecifiedRequestType,
|
||||
)
|
||||
from delta_barth.logging import logger_session as logger
|
||||
from delta_barth.types import (
|
||||
ApiCredentials,
|
||||
DelBarApiError,
|
||||
HttpRequestTypes,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from delta_barth.types import HttpContentHeaders, Status
|
||||
|
||||
# ** login
|
||||
class LoginRequest(BaseModel):
|
||||
userName: str
|
||||
password: str
|
||||
databaseName: str
|
||||
mandantName: str
|
||||
|
||||
|
||||
class Session:
|
||||
def __init__(
|
||||
self,
|
||||
base_headers: HttpContentHeaders,
|
||||
logging_folder: str = "logs",
|
||||
) -> None:
|
||||
self._data_path: Path | None = None
|
||||
self._logging_dir: Path | None = None
|
||||
self._logging_folder = logging_folder
|
||||
self._creds: ApiCredentials | None = None
|
||||
self._base_url: str | None = None
|
||||
self._headers = base_headers
|
||||
self._session_token: str | None = None
|
||||
self._logged_in: bool = False
|
||||
|
||||
def setup(self) -> None:
|
||||
self.setup_logging()
|
||||
|
||||
@property
|
||||
def data_path(self) -> Path:
|
||||
assert self._data_path is not None, "accessed data path not set"
|
||||
return self._data_path
|
||||
|
||||
@property
|
||||
def logging_dir(self) -> Path:
|
||||
if self._logging_dir is not None:
|
||||
return self._logging_dir
|
||||
|
||||
logging_dir = self.data_path / self._logging_folder
|
||||
if not logging_dir.exists():
|
||||
logging_dir.mkdir(parents=False)
|
||||
self._logging_dir = logging_dir
|
||||
return self._logging_dir
|
||||
|
||||
def setup_logging(self) -> None:
|
||||
delta_barth.logging.setup_logging(self.logging_dir)
|
||||
logger.info("[SESSION] Successfully setup logging")
|
||||
|
||||
@property
|
||||
def creds(self) -> ApiCredentials:
|
||||
assert self._creds is not None, "accessed credentials not set"
|
||||
return self._creds
|
||||
|
||||
def set_data_path(
|
||||
self,
|
||||
path: str,
|
||||
):
|
||||
self._data_path = validate_path(path)
|
||||
|
||||
def set_credentials(
|
||||
self,
|
||||
username: str,
|
||||
password: str,
|
||||
database: str,
|
||||
mandant: str,
|
||||
) -> None:
|
||||
if self.logged_in:
|
||||
self.logout()
|
||||
self._creds = validate_credentials(
|
||||
username=username,
|
||||
password=password,
|
||||
database=database,
|
||||
mandant=mandant,
|
||||
)
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
assert self._base_url is not None, "accessed base URL not set"
|
||||
return self._base_url
|
||||
|
||||
def set_base_url(
|
||||
self,
|
||||
base_url: str,
|
||||
) -> None:
|
||||
if self.logged_in:
|
||||
self.logout()
|
||||
self._base_url = base_url
|
||||
|
||||
@property
|
||||
def headers(self) -> HttpContentHeaders:
|
||||
return self._headers
|
||||
|
||||
@property
|
||||
def session_token(self) -> str | None:
|
||||
return self._session_token
|
||||
|
||||
@property
|
||||
def logged_in(self) -> bool:
|
||||
return self._logged_in
|
||||
|
||||
def _add_session_token(
|
||||
self,
|
||||
token: str,
|
||||
) -> None:
|
||||
assert self.session_token is None, "tried overwriting existing API session token"
|
||||
self._session_token = token
|
||||
self._headers.update(DelecoToken=token)
|
||||
self._logged_in = True
|
||||
|
||||
def _remove_session_token(self) -> None:
|
||||
assert self.session_token is not None, (
|
||||
"tried to delete non-existing API session token"
|
||||
)
|
||||
if "DelecoToken" in self.headers:
|
||||
del self._headers["DelecoToken"]
|
||||
self._session_token = None
|
||||
self._logged_in = False
|
||||
|
||||
def login(
|
||||
self,
|
||||
) -> tuple[LoginResponse, Status]:
|
||||
ROUTE: Final[str] = "user/login"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
|
||||
login_req = LoginRequest(
|
||||
userName=self.creds.username,
|
||||
password=self.creds.password,
|
||||
databaseName=self.creds.database,
|
||||
mandantName=self.creds.mandant,
|
||||
)
|
||||
resp = requests.put(
|
||||
URL,
|
||||
login_req.model_dump_json(),
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
response = LoginResponse(**resp.json())
|
||||
status = STATUS_HANDLER.pipe_states.SUCCESS
|
||||
self._add_session_token(response.token)
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
|
||||
def logout(
|
||||
self,
|
||||
) -> tuple[None, Status]:
|
||||
ROUTE: Final[str] = "user/logout"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
|
||||
resp = requests.put(
|
||||
URL,
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
|
||||
response = None
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
self._remove_session_token()
|
||||
else:
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
|
||||
def assert_login(
|
||||
self,
|
||||
) -> tuple[LoginResponse, Status]:
|
||||
# check if login token is still valid
|
||||
# re-login if necessary
|
||||
if self.session_token is None:
|
||||
return self.login()
|
||||
|
||||
# use known endpoint which requires a valid token in its header
|
||||
# evaluate the response to decide if:
|
||||
# current token is still valid, token is not valid, other errors occurred
|
||||
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
|
||||
)
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
response = LoginResponse(token=self.session_token)
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
elif resp.status_code == 401:
|
||||
self._remove_session_token()
|
||||
response, status = self.login()
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
|
||||
|
||||
def validate_path(
|
||||
str_path: str,
|
||||
) -> Path:
|
||||
path = Path(str_path).resolve()
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Provided path >{path}< seems not to exist.")
|
||||
elif not path.is_dir():
|
||||
raise FileNotFoundError(f"Provided path >{path}< seems not to be a directory.")
|
||||
|
||||
return path
|
||||
class LoginResponse(BaseModel):
|
||||
token: str
|
||||
|
||||
|
||||
def validate_credentials(
|
||||
@@ -265,15 +60,3 @@ def ping(
|
||||
raise UnspecifiedRequestType(f"Request type {method} not defined for endpoint")
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
# ** login
|
||||
class LoginRequest(BaseModel):
|
||||
userName: str
|
||||
password: str
|
||||
databaseName: str
|
||||
mandantName: str
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
token: str
|
||||
|
||||
@@ -11,7 +11,7 @@ from delta_barth.errors import STATUS_HANDLER
|
||||
from delta_barth.types import DelBarApiError, ExportResponse, ResponseType, Status
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from delta_barth.api.common import Session
|
||||
from delta_barth.session import Session
|
||||
|
||||
|
||||
# ** sales data
|
||||
|
||||
@@ -5,8 +5,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from delta_barth.api.common import Session
|
||||
from delta_barth.constants import HTTP_BASE_CONTENT_HEADERS
|
||||
from delta_barth.session import Session
|
||||
|
||||
SESSION: Final[Session] = Session(HTTP_BASE_CONTENT_HEADERS)
|
||||
|
||||
|
||||
229
src/delta_barth/session.py
Normal file
229
src/delta_barth/session.py
Normal file
@@ -0,0 +1,229 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
import requests
|
||||
from dopt_basics.io import combine_route
|
||||
|
||||
import delta_barth.logging
|
||||
from delta_barth.api.common import (
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
validate_credentials,
|
||||
)
|
||||
from delta_barth.errors import STATUS_HANDLER
|
||||
from delta_barth.logging import logger_session as logger
|
||||
from delta_barth.types import DelBarApiError, Status
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from delta_barth.types import ApiCredentials, HttpContentHeaders
|
||||
|
||||
|
||||
def validate_path(
|
||||
str_path: str,
|
||||
) -> Path:
|
||||
path = Path(str_path).resolve()
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Provided path >{path}< seems not to exist.")
|
||||
elif not path.is_dir():
|
||||
raise FileNotFoundError(f"Provided path >{path}< seems not to be a directory.")
|
||||
|
||||
return path
|
||||
|
||||
|
||||
class Session:
|
||||
def __init__(
|
||||
self,
|
||||
base_headers: HttpContentHeaders,
|
||||
logging_folder: str = "logs",
|
||||
) -> None:
|
||||
self._data_path: Path | None = None
|
||||
self._logging_dir: Path | None = None
|
||||
self._logging_folder = logging_folder
|
||||
self._creds: ApiCredentials | None = None
|
||||
self._base_url: str | None = None
|
||||
self._headers = base_headers
|
||||
self._session_token: str | None = None
|
||||
self._logged_in: bool = False
|
||||
|
||||
def setup(self) -> None:
|
||||
self.setup_logging()
|
||||
|
||||
@property
|
||||
def data_path(self) -> Path:
|
||||
assert self._data_path is not None, "accessed data path not set"
|
||||
return self._data_path
|
||||
|
||||
@property
|
||||
def logging_dir(self) -> Path:
|
||||
if self._logging_dir is not None:
|
||||
return self._logging_dir
|
||||
|
||||
logging_dir = self.data_path / self._logging_folder
|
||||
if not logging_dir.exists():
|
||||
logging_dir.mkdir(parents=False)
|
||||
self._logging_dir = logging_dir
|
||||
return self._logging_dir
|
||||
|
||||
def setup_logging(self) -> None:
|
||||
delta_barth.logging.setup_logging(self.logging_dir)
|
||||
logger.info("[SESSION] Successfully setup logging")
|
||||
|
||||
@property
|
||||
def creds(self) -> ApiCredentials:
|
||||
assert self._creds is not None, "accessed credentials not set"
|
||||
return self._creds
|
||||
|
||||
def set_data_path(
|
||||
self,
|
||||
path: str,
|
||||
):
|
||||
self._data_path = validate_path(path)
|
||||
|
||||
def set_credentials(
|
||||
self,
|
||||
username: str,
|
||||
password: str,
|
||||
database: str,
|
||||
mandant: str,
|
||||
) -> None:
|
||||
if self.logged_in:
|
||||
self.logout()
|
||||
self._creds = validate_credentials(
|
||||
username=username,
|
||||
password=password,
|
||||
database=database,
|
||||
mandant=mandant,
|
||||
)
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
assert self._base_url is not None, "accessed base URL not set"
|
||||
return self._base_url
|
||||
|
||||
def set_base_url(
|
||||
self,
|
||||
base_url: str,
|
||||
) -> None:
|
||||
if self.logged_in:
|
||||
self.logout()
|
||||
self._base_url = base_url
|
||||
|
||||
@property
|
||||
def headers(self) -> HttpContentHeaders:
|
||||
return self._headers
|
||||
|
||||
@property
|
||||
def session_token(self) -> str | None:
|
||||
return self._session_token
|
||||
|
||||
@property
|
||||
def logged_in(self) -> bool:
|
||||
return self._logged_in
|
||||
|
||||
def _add_session_token(
|
||||
self,
|
||||
token: str,
|
||||
) -> None:
|
||||
assert self.session_token is None, "tried overwriting existing API session token"
|
||||
self._session_token = token
|
||||
self._headers.update(DelecoToken=token)
|
||||
self._logged_in = True
|
||||
|
||||
def _remove_session_token(self) -> None:
|
||||
assert self.session_token is not None, (
|
||||
"tried to delete non-existing API session token"
|
||||
)
|
||||
if "DelecoToken" in self.headers:
|
||||
del self._headers["DelecoToken"]
|
||||
self._session_token = None
|
||||
self._logged_in = False
|
||||
|
||||
def login(
|
||||
self,
|
||||
) -> tuple[LoginResponse, Status]:
|
||||
ROUTE: Final[str] = "user/login"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
|
||||
login_req = LoginRequest(
|
||||
userName=self.creds.username,
|
||||
password=self.creds.password,
|
||||
databaseName=self.creds.database,
|
||||
mandantName=self.creds.mandant,
|
||||
)
|
||||
resp = requests.put(
|
||||
URL,
|
||||
login_req.model_dump_json(),
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
response = LoginResponse(**resp.json())
|
||||
status = STATUS_HANDLER.pipe_states.SUCCESS
|
||||
self._add_session_token(response.token)
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
|
||||
def logout(
|
||||
self,
|
||||
) -> tuple[None, Status]:
|
||||
ROUTE: Final[str] = "user/logout"
|
||||
URL: Final = combine_route(self.base_url, ROUTE)
|
||||
|
||||
resp = requests.put(
|
||||
URL,
|
||||
headers=self.headers, # type: ignore
|
||||
)
|
||||
|
||||
response = None
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
self._remove_session_token()
|
||||
else:
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
|
||||
def assert_login(
|
||||
self,
|
||||
) -> tuple[LoginResponse, Status]:
|
||||
# check if login token is still valid
|
||||
# re-login if necessary
|
||||
if self.session_token is None:
|
||||
return self.login()
|
||||
|
||||
# use known endpoint which requires a valid token in its header
|
||||
# evaluate the response to decide if:
|
||||
# current token is still valid, token is not valid, other errors occurred
|
||||
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
|
||||
)
|
||||
|
||||
response: LoginResponse
|
||||
status: Status
|
||||
if resp.status_code == 200:
|
||||
response = LoginResponse(token=self.session_token)
|
||||
status = STATUS_HANDLER.SUCCESS
|
||||
elif resp.status_code == 401:
|
||||
self._remove_session_token()
|
||||
response, status = self.login()
|
||||
else:
|
||||
response = LoginResponse(token="")
|
||||
err = DelBarApiError(status_code=resp.status_code, **resp.json())
|
||||
status = STATUS_HANDLER.api_error(err)
|
||||
|
||||
return response, status
|
||||
Reference in New Issue
Block a user