Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1447752970 | |||
| 4072b97012 | |||
| a1057fc78b | |||
| 214659c7f1 |
24
pdm.lock
generated
24
pdm.lock
generated
@@ -5,7 +5,7 @@
|
||||
groups = ["default", "dev", "lint", "nb", "tests"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:545c39ef89d18d28a7bca4b08c93e6fb900c42612089300b867a4e0955acd6ab"
|
||||
content_hash = "sha256:c3fd178d5c4736852fff59e2e4c5e3565b0fb80bf29ec5979e1e9c78d452ee1f"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = ">=3.11"
|
||||
@@ -1623,7 +1623,7 @@ name = "psutil"
|
||||
version = "7.0.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
|
||||
groups = ["nb"]
|
||||
groups = ["default", "nb"]
|
||||
files = [
|
||||
{file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
|
||||
{file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
|
||||
@@ -2611,8 +2611,8 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "xgboost"
|
||||
version = "2.1.4"
|
||||
requires_python = ">=3.8"
|
||||
version = "3.0.0"
|
||||
requires_python = ">=3.10"
|
||||
summary = "XGBoost Python Package"
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
@@ -2621,12 +2621,12 @@ dependencies = [
|
||||
"scipy",
|
||||
]
|
||||
files = [
|
||||
{file = "xgboost-2.1.4-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:78d88da184562deff25c820d943420342014dd55e0f4c017cc4563c2148df5ee"},
|
||||
{file = "xgboost-2.1.4-py3-none-macosx_12_0_arm64.whl", hash = "sha256:523db01d4e74b05c61a985028bde88a4dd380eadc97209310621996d7d5d14a7"},
|
||||
{file = "xgboost-2.1.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:57c7e98111aceef4b689d7d2ce738564a1f7fe44237136837a47847b8b33bade"},
|
||||
{file = "xgboost-2.1.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1343a512e634822eab30d300bfc00bf777dc869d881cc74854b42173cfcdb14"},
|
||||
{file = "xgboost-2.1.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d366097d0db047315736f46af852feaa907f6d7371716af741cdce488ae36d20"},
|
||||
{file = "xgboost-2.1.4-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8df6da72963969ab2bf49a520c3e147b1e15cbeddd3aa0e3e039b3532c739339"},
|
||||
{file = "xgboost-2.1.4-py3-none-win_amd64.whl", hash = "sha256:8bbfe4fedc151b83a52edbf0de945fd94358b09a81998f2945ad330fd5f20cd6"},
|
||||
{file = "xgboost-2.1.4.tar.gz", hash = "sha256:ab84c4bbedd7fae1a26f61e9dd7897421d5b08454b51c6eb072abc1d346d08d7"},
|
||||
{file = "xgboost-3.0.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:ed8cffd7998bd9431c3b0287a70bec8e45c09b43c9474d9dfd261627713bd890"},
|
||||
{file = "xgboost-3.0.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:314104bd3a1426a40f0c9662eef40e9ab22eb7a8068a42a8d198ce40412db75c"},
|
||||
{file = "xgboost-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72c3405e8dfc37048f9fe339a058fa12b9f0f03bc31d3e56f0887eed2ed2baa1"},
|
||||
{file = "xgboost-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:72d39e74649e9b628c4221111aa6a8caa860f2e853b25480424403ee61085126"},
|
||||
{file = "xgboost-3.0.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7bdee5787f86b83bebd75e2c96caf854760788e5f4203d063da50db5bf0efc5f"},
|
||||
{file = "xgboost-3.0.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:61c7e391e373b8a5312503525c0689f83ef1912a1236377022865ab340f465a4"},
|
||||
{file = "xgboost-3.0.0-py3-none-win_amd64.whl", hash = "sha256:0ea74e97f95b1eddfd27a46b7f22f72ec5a5322e1dc7cb41c9c23fb580763df9"},
|
||||
{file = "xgboost-3.0.0.tar.gz", hash = "sha256:45e95416df6f6f01d9a62e60cf09fc57e5ee34697f3858337c796fac9ce3b9ed"},
|
||||
]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[project]
|
||||
name = "delta-barth"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
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"},
|
||||
]
|
||||
dependencies = ["scikit-learn>=1.6.1", "pandas>=2.2.3", "xgboost>=2.1.4", "joblib>=1.4.2", "typing-extensions>=4.12.2", "requests>=2.32.3", "pydantic>=2.10.6", "dopt-basics>=0.1.3", "SQLAlchemy>=2.0.39"]
|
||||
dependencies = ["scikit-learn>=1.6.1", "pandas>=2.2.3", "xgboost>=2.1.4", "joblib>=1.4.2", "typing-extensions>=4.12.2", "requests>=2.32.3", "pydantic>=2.10.6", "dopt-basics>=0.1.3", "SQLAlchemy>=2.0.39", "psutil>=7.0.0"]
|
||||
requires-python = ">=3.11"
|
||||
readme = "README.md"
|
||||
license = {text = "LicenseRef-Proprietary"}
|
||||
@@ -74,7 +74,7 @@ directory = "reports/coverage"
|
||||
|
||||
|
||||
[tool.bumpversion]
|
||||
current_version = "0.5.7"
|
||||
current_version = "0.5.8"
|
||||
parse = """(?x)
|
||||
(?P<major>0|[1-9]\\d*)\\.
|
||||
(?P<minor>0|[1-9]\\d*)\\.
|
||||
|
||||
33
src/delta_barth/_env.py
Normal file
33
src/delta_barth/_env.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from dopt_basics import io
|
||||
|
||||
PY_RUNTIME_FOLDER: Final[str] = "python"
|
||||
|
||||
|
||||
def prepare_env(
|
||||
lib_path: Path,
|
||||
) -> Path | None:
|
||||
pyrt_folder = io.search_folder_path(
|
||||
starting_path=lib_path,
|
||||
stop_folder_name=PY_RUNTIME_FOLDER,
|
||||
return_inclusive=True,
|
||||
)
|
||||
if pyrt_folder is None:
|
||||
return None
|
||||
|
||||
pth_interpreter = pyrt_folder / "python.exe"
|
||||
if not pth_interpreter.exists():
|
||||
raise FileNotFoundError(
|
||||
f"dopt-delta-barth seems to be deployed in a standalone runtime, "
|
||||
f"but the interpreter was not found under: {pth_interpreter}"
|
||||
)
|
||||
|
||||
setattr(sys, "executable", str(pth_interpreter))
|
||||
setattr(sys, "_base_executable", str(pth_interpreter))
|
||||
|
||||
return pyrt_folder
|
||||
@@ -8,6 +8,11 @@ from dataclasses import asdict
|
||||
from datetime import datetime as Datetime
|
||||
from typing import TYPE_CHECKING, Final, TypeAlias, cast
|
||||
|
||||
import joblib
|
||||
import joblib.externals
|
||||
import joblib.externals.loky
|
||||
import joblib.externals.loky.backend
|
||||
import joblib.externals.loky.backend.popen_loky_win32
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import scipy.stats
|
||||
@@ -33,6 +38,7 @@ from delta_barth.constants import (
|
||||
DEFAULT_DB_ERR_CODE,
|
||||
DUMMY_DATA_PATH,
|
||||
FEATURES_SALES_PROGNOSIS,
|
||||
MAX_NUM_WORKERS,
|
||||
SALES_MIN_NUM_DATAPOINTS,
|
||||
)
|
||||
from delta_barth.errors import STATUS_HANDLER, wrap_result
|
||||
@@ -291,18 +297,22 @@ def _process_sales(
|
||||
if len(train[train[SALES_FEAT] > 0]) >= (base_num_data_points_months + 10 * add_year):
|
||||
too_few_month_points = False
|
||||
|
||||
rand = RandomizedSearchCV(
|
||||
XGBRegressor(),
|
||||
params,
|
||||
scoring="neg_mean_absolute_error",
|
||||
cv=kfold,
|
||||
n_jobs=-1,
|
||||
n_iter=100,
|
||||
verbose=0,
|
||||
)
|
||||
rand.fit(
|
||||
X_train, y_train, eval_set=[(X_train, y_train), (X_test, y_test)], verbose=0
|
||||
)
|
||||
with joblib.parallel_config(backend="loky"):
|
||||
rand = RandomizedSearchCV(
|
||||
XGBRegressor(),
|
||||
params,
|
||||
scoring="neg_mean_absolute_error",
|
||||
cv=kfold,
|
||||
n_jobs=MAX_NUM_WORKERS,
|
||||
n_iter=100,
|
||||
verbose=0,
|
||||
)
|
||||
rand.fit(
|
||||
X_train,
|
||||
y_train,
|
||||
eval_set=[(X_train, y_train), (X_test, y_test)],
|
||||
verbose=0,
|
||||
)
|
||||
y_pred = rand.best_estimator_.predict(X_test) # type: ignore
|
||||
|
||||
if len(np.unique(y_pred)) != 1:
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
import psutil
|
||||
|
||||
import delta_barth._env
|
||||
from delta_barth.types import DualDict, HttpContentHeaders
|
||||
|
||||
# ** config
|
||||
CFG_FILENAME: Final[str] = "dopt-cfg.toml"
|
||||
CFG_HOT_RELOAD: Final[bool] = True
|
||||
cpu_count = psutil.cpu_count(logical=False)
|
||||
MAX_NUM_WORKERS: Final[int] = (cpu_count - 1) if cpu_count is not None else 3
|
||||
|
||||
# ** lib path
|
||||
lib_path = Path(__file__).parent
|
||||
@@ -14,12 +22,13 @@ LIB_PATH: Final[Path] = lib_path
|
||||
dummy_data_pth = LIB_PATH / "_dummy_data"
|
||||
assert dummy_data_pth.exists(), f"dummy data path not found: {dummy_data_pth}"
|
||||
DUMMY_DATA_PATH: Final[Path] = dummy_data_pth
|
||||
# ** runtime and deployment status
|
||||
RUNTIME_PATH: Final[Path | None] = delta_barth._env.prepare_env(LIB_PATH)
|
||||
deployment_status: bool = False
|
||||
if RUNTIME_PATH is not None:
|
||||
deployment_status = True
|
||||
DEPLOYMENT_STATUS: Final[bool] = deployment_status
|
||||
|
||||
# ** logging
|
||||
ENABLE_LOGGING: Final[bool] = True
|
||||
LOGGING_TO_FILE: Final[bool] = True
|
||||
LOGGING_TO_STDERR: Final[bool] = False
|
||||
LOG_FILENAME: Final[str] = "dopt-delbar.log"
|
||||
|
||||
# ** databases
|
||||
DB_ECHO: Final[bool] = True
|
||||
|
||||
@@ -6,14 +6,13 @@ from pathlib import Path
|
||||
from time import gmtime
|
||||
from typing import Final
|
||||
|
||||
from delta_barth.constants import (
|
||||
ENABLE_LOGGING,
|
||||
LOG_FILENAME,
|
||||
LOGGING_TO_FILE,
|
||||
LOGGING_TO_STDERR,
|
||||
)
|
||||
|
||||
# ** config
|
||||
# ** logging
|
||||
ENABLE_LOGGING: Final[bool] = True
|
||||
LOGGING_TO_FILE: Final[bool] = True
|
||||
LOGGING_TO_STDERR: Final[bool] = False
|
||||
LOG_FILENAME: Final[str] = "dopt-delbar.log"
|
||||
|
||||
logging.Formatter.converter = gmtime
|
||||
LOG_FMT: Final[str] = "%(asctime)s | lang_main:%(module)s:%(levelname)s | %(message)s"
|
||||
LOG_DATE_FMT: Final[str] = "%Y-%m-%d %H:%M:%S +0000"
|
||||
|
||||
@@ -19,6 +19,7 @@ from delta_barth.config import LazyCfgLoader
|
||||
from delta_barth.constants import (
|
||||
API_CON_TIMEOUT,
|
||||
CFG_FILENAME,
|
||||
CFG_HOT_RELOAD,
|
||||
DB_ECHO,
|
||||
LIB_PATH,
|
||||
)
|
||||
@@ -97,6 +98,8 @@ class Session:
|
||||
@property
|
||||
def cfg(self) -> Config:
|
||||
assert self._cfg is not None, "tried to access not set config from session"
|
||||
if CFG_HOT_RELOAD:
|
||||
self.reload_cfg()
|
||||
return self._cfg
|
||||
|
||||
def _setup_config(self) -> None:
|
||||
@@ -108,6 +111,11 @@ class Session:
|
||||
self._cfg = self._cfg_loader.get()
|
||||
logger.info("[SESSION] Successfully read and setup config")
|
||||
|
||||
def reload_cfg(self) -> None:
|
||||
assert self._cfg_loader is not None, "tried reloading with no CFG loader intialised"
|
||||
self._cfg_loader.reload()
|
||||
self._cfg = self._cfg_loader.get()
|
||||
|
||||
@property
|
||||
def db_engine(self) -> sql.Engine:
|
||||
assert self._db_engine is not None, "accessed database engine not set"
|
||||
|
||||
@@ -430,6 +430,7 @@ def test_export_on_fail():
|
||||
assert res.status.description == status.description
|
||||
|
||||
|
||||
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
||||
def test_pipeline_sales_forecast_SuccessDbWrite(exmpl_api_sales_prognosis_resp, session):
|
||||
with (
|
||||
patch(
|
||||
|
||||
49
tests/test_env.py
Normal file
49
tests/test_env.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import importlib
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import delta_barth.constants
|
||||
from delta_barth import _env
|
||||
|
||||
|
||||
@patch("delta_barth._env.PY_RUNTIME_FOLDER", "test123456")
|
||||
def test_prepare_env_NoRuntimeFolder(tmp_path):
|
||||
ret = _env.prepare_env(tmp_path)
|
||||
assert ret is None
|
||||
|
||||
|
||||
@patch("delta_barth._env.PY_RUNTIME_FOLDER", "base")
|
||||
def test_prepare_env_FailNoInterpreter(tmp_path_factory):
|
||||
mocked_lib_pth = tmp_path_factory.mktemp("path") / "to/base/folder/lib/"
|
||||
mocked_lib_pth.mkdir(parents=True, exist_ok=True)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
_ = _env.prepare_env(mocked_lib_pth)
|
||||
|
||||
|
||||
@patch("delta_barth._env.PY_RUNTIME_FOLDER", "base")
|
||||
def test_prepare_env_Success(tmp_path_factory):
|
||||
mocked_lib_pth = tmp_path_factory.mktemp("path") / "to/base/folder/lib/"
|
||||
mocked_lib_pth.mkdir(parents=True, exist_ok=True)
|
||||
rt_path = mocked_lib_pth.parents[1]
|
||||
mocked_interpreter = rt_path / "python.exe"
|
||||
mocked_interpreter.touch()
|
||||
assert mocked_interpreter.exists()
|
||||
ret = _env.prepare_env(mocked_lib_pth)
|
||||
assert ret == rt_path
|
||||
# sys attributes
|
||||
executable = getattr(sys, "executable")
|
||||
assert executable == str(mocked_interpreter)
|
||||
base_executable = getattr(sys, "_base_executable")
|
||||
assert base_executable == str(mocked_interpreter)
|
||||
|
||||
class MockPath:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.parent = mocked_lib_pth
|
||||
|
||||
with patch("pathlib.Path", MockPath):
|
||||
(mocked_lib_pth / "_dummy_data").mkdir(exist_ok=True)
|
||||
importlib.reload(delta_barth.constants)
|
||||
assert delta_barth.constants.DEPLOYMENT_STATUS
|
||||
assert delta_barth.constants.RUNTIME_PATH == rt_path
|
||||
@@ -45,6 +45,7 @@ def test_write_performance_metrics_FailStartingTime(session):
|
||||
)
|
||||
|
||||
|
||||
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
||||
def test_sales_prognosis_pipeline(exmpl_api_sales_prognosis_resp, session, monkeypatch):
|
||||
with (
|
||||
patch(
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import tomli_w
|
||||
|
||||
import delta_barth.config
|
||||
import delta_barth.session
|
||||
from delta_barth import logging
|
||||
from delta_barth.constants import (
|
||||
DEFAULT_API_ERR_CODE,
|
||||
HTTP_BASE_CONTENT_HEADERS,
|
||||
LOG_FILENAME,
|
||||
)
|
||||
from delta_barth.logging import LOG_FILENAME
|
||||
|
||||
|
||||
def test_validate_path_Success():
|
||||
@@ -62,7 +65,7 @@ def test_session_setup_db_management(tmp_path):
|
||||
assert db_path.exists()
|
||||
|
||||
|
||||
def test_session_setup_config(tmp_path, pth_cfg):
|
||||
def test_session_setup_config(tmp_path):
|
||||
str_path = str(tmp_path)
|
||||
foldername: str = "cfg_test"
|
||||
target_cfg_dir = tmp_path / foldername
|
||||
@@ -80,6 +83,61 @@ def test_session_setup_config(tmp_path, pth_cfg):
|
||||
assert session.cfg.forecast.threshold_month_data_points == 28
|
||||
|
||||
|
||||
@patch("delta_barth.session.CFG_HOT_RELOAD", False)
|
||||
def test_session_reload_config_NoHotReload(tmp_path):
|
||||
str_path = str(tmp_path)
|
||||
foldername: str = "cfg_test"
|
||||
target_cfg_dir = tmp_path / foldername
|
||||
session = delta_barth.session.Session(HTTP_BASE_CONTENT_HEADERS, cfg_folder=foldername)
|
||||
session.set_data_path(str_path)
|
||||
cfg_path = session.cfg_path
|
||||
assert cfg_path.parent.exists()
|
||||
assert cfg_path.parent == target_cfg_dir
|
||||
assert not cfg_path.exists()
|
||||
session.setup()
|
||||
assert cfg_path.exists()
|
||||
parsed_cfg = session.cfg
|
||||
assert isinstance(parsed_cfg, delta_barth.config.Config)
|
||||
# modify config and reload
|
||||
with open(cfg_path, "rb") as file:
|
||||
cfg_data = tomllib.load(file)
|
||||
cfg_data["forecast"]["threshold_month_data_points"] = 30
|
||||
with open(cfg_path, "wb") as file:
|
||||
tomli_w.dump(cfg_data, file)
|
||||
|
||||
assert session.cfg.forecast.threshold_month_data_points == 28
|
||||
|
||||
session.reload_cfg()
|
||||
reload_cfg = session.cfg
|
||||
assert isinstance(reload_cfg, delta_barth.config.Config)
|
||||
assert reload_cfg.forecast.threshold_month_data_points == 30
|
||||
|
||||
|
||||
@patch("delta_barth.session.CFG_HOT_RELOAD", True)
|
||||
def test_session_reload_config_HotReload(tmp_path):
|
||||
str_path = str(tmp_path)
|
||||
foldername: str = "cfg_test"
|
||||
target_cfg_dir = tmp_path / foldername
|
||||
session = delta_barth.session.Session(HTTP_BASE_CONTENT_HEADERS, cfg_folder=foldername)
|
||||
session.set_data_path(str_path)
|
||||
cfg_path = session.cfg_path
|
||||
assert cfg_path.parent.exists()
|
||||
assert cfg_path.parent == target_cfg_dir
|
||||
assert not cfg_path.exists()
|
||||
session.setup()
|
||||
assert cfg_path.exists()
|
||||
parsed_cfg = session.cfg
|
||||
assert isinstance(parsed_cfg, delta_barth.config.Config)
|
||||
# modify config and reload
|
||||
with open(cfg_path, "rb") as file:
|
||||
cfg_data = tomllib.load(file)
|
||||
cfg_data["forecast"]["threshold_month_data_points"] = 30
|
||||
with open(cfg_path, "wb") as file:
|
||||
tomli_w.dump(cfg_data, file)
|
||||
|
||||
assert session.cfg.forecast.threshold_month_data_points == 30
|
||||
|
||||
|
||||
@patch("delta_barth.logging.ENABLE_LOGGING", True)
|
||||
@patch("delta_barth.logging.LOGGING_TO_FILE", True)
|
||||
@patch("delta_barth.logging.LOGGING_TO_STDERR", True)
|
||||
|
||||
Reference in New Issue
Block a user