add basic functionality and corresponding tests

This commit is contained in:
2025-03-13 16:36:18 +01:00
parent 48881e882c
commit 2db39b536e
15 changed files with 1026 additions and 2 deletions

View File

@@ -0,0 +1,2 @@
[test]
entry = 'test123'

12
tests/conftest.py Normal file
View File

@@ -0,0 +1,12 @@
from pathlib import Path
import pytest
@pytest.fixture(scope="session")
def root_data_folder() -> Path:
pth = Path.cwd() / "tests/_test_data/"
assert pth.exists()
assert pth.is_dir()
return pth

36
tests/test_configs.py Normal file
View File

@@ -0,0 +1,36 @@
from pathlib import Path
import pytest
from dopt_basics import configs
@pytest.fixture(scope="module")
def config_file(root_data_folder) -> Path:
pth = root_data_folder / "config.toml"
assert pth.exists()
assert pth.is_file()
return pth
def test_load_toml_SuccessPath(config_file):
cfg = configs.load_toml(config_file)
assert isinstance(cfg, dict)
assert "test" in cfg
assert cfg["test"]["entry"] == "test123"
def test_load_toml_SuccessStringPath(config_file):
str_pth = str(config_file)
cfg = configs.load_toml(str_pth)
assert isinstance(cfg, dict)
assert "test" in cfg
assert cfg["test"]["entry"] == "test123"
def test_load_toml_FailWrongPath(tmp_path):
wrong_pth = tmp_path / "config.toml"
with pytest.raises(FileNotFoundError):
_ = configs.load_toml(wrong_pth)

View File

@@ -0,0 +1,64 @@
import pytest
from dopt_basics import datastructures as dst
def test_flatten():
nested_iterable = ([1, 2], [[3], [4, 5]], [6, [7, 8, 9]])
target = tuple(i for i in range(1, 10))
ret_iter = dst.flatten(nested_iterable)
ret = tuple(ret_iter)
assert ret == target
def test_DualDict():
base_dict: dict[str, int] = {"test1": 1, "test2": 2, "test3": 3}
inverted_dict: dict[int, str] = {1: "test1", 2: "test2", 3: "test3"}
assert all((key == inverted_dict[value] for key, value in base_dict.items()))
dual_dict: dst.DualDict[str, int] = dst.DualDict(test1=1, test2=2, test3=3)
assert all((key in dual_dict for key in base_dict.keys()))
assert all((base_dict[key] == dual_dict[key] for key in base_dict.keys()))
assert all((key == dual_dict.inverted[value] for key, value in base_dict.items()))
assert all(
(inverted_dict[key] == dual_dict.inverted[key] for key in inverted_dict.keys())
)
base_dict["test_add"] = 5
dual_dict["test_add"] = 5
assert len(base_dict) == len(dual_dict)
assert len(dual_dict) == len(dual_dict.inverted)
del base_dict["test_add"]
del dual_dict["test_add"]
assert len(base_dict) == len(dual_dict)
assert len(dual_dict) == len(dual_dict.inverted)
for key_base, key_dd in zip(base_dict, dual_dict):
assert key_base == key_dd
def test_DualDict_update_Success():
base_dict: dict[str, int] = {"test1": 1, "test2": 2, "test3": 3}
dual_dict: dst.DualDict[str, int] = dst.DualDict(test1=1, test2=2, test3=3)
update = dict(test3=4, test4=5)
base_dict.update(**update)
dual_dict.update(**update)
assert all((key in dual_dict for key in base_dict.keys()))
assert all((base_dict[key] == dual_dict[key] for key in base_dict.keys()))
assert all((key == dual_dict.inverted[value] for key, value in base_dict.items()))
def test_DualDict_update_FailIdenticalValues():
base_dict: dict[str, int] = {"test1": 1, "test2": 2, "test3": 3}
with pytest.raises(ValueError):
_: dst.DualDict[str, int] = dst.DualDict(test1=1, test2=3, test3=3)
dual_dict: dst.DualDict[str, int] = dst.DualDict(test1=1, test2=2, test3=3)
update = dict(test3=4, test4=4)
base_dict.update(**update)
with pytest.raises(ValueError):
dual_dict.update(**update)

148
tests/test_datetime.py Normal file
View File

@@ -0,0 +1,148 @@
from datetime import UTC, datetime, timedelta
from unittest.mock import patch
import pytest
from dopt_basics import datetime as datetime_
from dopt_basics.datetime import TIMEZONE_CEST, TimeUnitsTimedelta
def test_dt_with_UTC():
year = 2024
month = 3
day = 28
hour = 3
minute = 0
dt_target = datetime(year, month, day, hour, minute, tzinfo=UTC)
dt_ret = datetime_.dt_with_tz_UTC(year, month, day, hour, minute)
assert dt_target == dt_ret
@pytest.mark.parametrize(
"time_unit, expected",
[
("hours", timedelta(hours=2.0)),
("minutes", timedelta(minutes=2.0)),
("seconds", timedelta(seconds=2.0)),
("milliseconds", timedelta(milliseconds=2.0)),
("microseconds", timedelta(microseconds=2.0)),
(TimeUnitsTimedelta.HOURS, timedelta(hours=2.0)),
(TimeUnitsTimedelta.MINUTES, timedelta(minutes=2.0)),
],
)
def test_timedelta_from_val_Success(time_unit, expected):
val = 2.0
td = datetime_.timedelta_from_val(val, time_unit)
assert td == expected
def test_timedelta_from_val_FailWrongTimeUnit():
val = 2.0
time_unit = "years"
with pytest.raises(ValueError):
datetime_.timedelta_from_val(val, time_unit) # type: ignore
def test_round_td_by_seconds():
hours = 2.0
minutes = 30.0
seconds = 30.0
microseconds = 600
td = timedelta(hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds)
rounded_td = datetime_.round_td_by_seconds(td, round_to_next_seconds=1)
assert rounded_td == timedelta(hours=2.0, minutes=30.0, seconds=30.0)
def test_current_time_tz():
tz = datetime_.TIMEZONE_UTC
mock_dt = datetime(2024, 6, 1, 12, 15, 30, 1000, tzinfo=tz)
with patch("dopt_basics.datetime.Datetime") as mock_obj:
mock_obj.now.return_value = mock_dt
ret = datetime_.current_time_tz(cut_microseconds=False)
assert ret.tzinfo is not None
assert ret == mock_dt
with patch("dopt_basics.datetime.Datetime") as mock_obj:
mock_obj.now.return_value = mock_dt
ret = datetime_.current_time_tz(cut_microseconds=True)
target = datetime(2024, 6, 1, 12, 15, 30, tzinfo=tz)
assert ret.tzinfo is not None
assert ret == target
def test_get_timestamp_WithTime():
tz = datetime_.TIMEZONE_UTC
mock_dt = datetime(2024, 6, 1, 12, 15, 30, 1000, tzinfo=tz)
with patch("dopt_basics.datetime.Datetime") as mock_obj:
mock_obj.now.return_value = mock_dt
ret = datetime_.get_timestamp(tz=tz, with_time=True)
target = "2024-06-01--12-15-30"
assert ret == target
def test_get_timestamp_WithoutTime():
tz = datetime_.TIMEZONE_UTC
mock_dt = datetime(2024, 6, 1, 12, 15, 30, 1000, tzinfo=tz)
with patch("dopt_basics.datetime.Datetime") as mock_obj:
mock_obj.now.return_value = mock_dt
ret = datetime_.get_timestamp(tz=tz, with_time=False)
target = "2024-06-01"
assert ret == target
def test_add_timedelta_FailWithoutTZInfo():
year = 2024
month = 3
day = 30
hour = 3
minute = 0
dt = datetime(year, month, day, hour, minute)
td = timedelta(hours=2.0)
with pytest.raises(ValueError):
datetime_.add_timedelta_with_tz(dt, td)
def test_add_timedelta_with_tz():
year = 2024
month = 3
day = 30
hour = 23
minute = 0
dt = datetime(year, month, day, hour, minute, tzinfo=TIMEZONE_CEST)
td = timedelta(hours=6.0)
new_dt = datetime_.add_timedelta_with_tz(dt, td)
assert new_dt == datetime(2024, 3, 31, 6, 0, tzinfo=TIMEZONE_CEST)
def test_validate_dt_UTC_Success():
dt = datetime(2024, 3, 30, 0, 0, tzinfo=UTC)
datetime_.validate_dt_UTC(dt)
def test_validate_dt_FailWrongTZInfo():
dt = datetime(2024, 3, 30, 0, 0, tzinfo=TIMEZONE_CEST)
with pytest.raises(ValueError):
datetime_.validate_dt_UTC(dt)
def test_dt_to_timezone_Success():
dt = datetime(2024, 3, 30, 2, 0, tzinfo=UTC)
new_dt = datetime_.dt_to_timezone(dt, TIMEZONE_CEST)
assert new_dt == datetime(2024, 3, 30, 3, tzinfo=TIMEZONE_CEST)
def test_dt_to_timezone_FailWithoutTZInfo():
dt = datetime(2024, 3, 30, 2, 0)
with pytest.raises(ValueError):
datetime_.dt_to_timezone(dt, TIMEZONE_CEST)
def test_cut_microseconds():
dt = datetime(2024, 3, 30, 2, 0, 0, 600)
new_dt = datetime_.cut_dt_microseconds(dt)
assert new_dt == datetime(2024, 3, 30, 2, 0, 0, 0)

17
tests/test_enums.py Normal file
View File

@@ -0,0 +1,17 @@
import enum
from dopt_basics import enums
def test_enum_str_values_as_frzset():
class TestEnum(enum.StrEnum):
T1 = enum.auto()
T2 = enum.auto()
T3 = enum.auto()
target_vals = frozenset(("t1", "t2", "t3"))
extracted_vals = enums.enum_str_values_as_frzset(TestEnum)
diff = target_vals.difference(extracted_vals)
assert len(diff) == 0

207
tests/test_paths.py Normal file
View File

@@ -0,0 +1,207 @@
from pathlib import Path
import pytest
from dopt_basics import paths
FILE_SEARCH = "test.txt"
@pytest.fixture(scope="module")
def base_folder(tmp_path_factory) -> Path:
folder_structure = "path/to/base/folder/"
pth = tmp_path_factory.mktemp("search")
pth = pth / folder_structure
pth.mkdir(parents=True, exist_ok=True)
return pth
@pytest.fixture(scope="module")
def target_file_pth(base_folder) -> Path:
# place in folder 'path' of TMP path
target_folder = base_folder.parents[2]
target_file = target_folder / FILE_SEARCH
with open(target_file, "w") as file:
file.write("TEST")
return target_file
@pytest.mark.parametrize(
"delete_existing",
[True, False],
)
def test_create_folder(tmp_path, delete_existing):
target_dir = tmp_path / "test"
assert not target_dir.exists()
paths.create_folder(target_dir, delete_existing=delete_existing)
assert target_dir.exists()
assert target_dir.is_dir()
paths.create_folder(target_dir, delete_existing=delete_existing)
assert target_dir.exists()
assert target_dir.is_dir()
def test_prepare_save_path_SuccessWithCreate(tmp_path):
base_folder = tmp_path
dirs = ("target", "dir")
filename = None
suffix = None
target_pth = tmp_path / "/".join(dirs)
res_pth = paths.prepare_save_path(base_folder, dirs, filename, suffix, create_folder=True)
assert res_pth.exists()
assert res_pth == target_pth
def test_prepare_save_path_SuccessWithCreateTimestamp(tmp_path):
base_folder = tmp_path
dirs = ("target", "dir")
filename = "test"
suffix = ".pkl"
res_pth = paths.prepare_save_path(
base_folder, dirs, filename, suffix, create_folder=True, include_timestamp=True
)
assert res_pth.parent.exists()
def test_prepare_save_path_SuccessWithoutCreate(tmp_path):
base_folder = tmp_path
dirs = ("target", "dir")
filename = None
suffix = None
target_pth = tmp_path / "/".join(dirs)
res_pth = paths.prepare_save_path(
base_folder, dirs, filename, suffix, create_folder=False
)
assert not res_pth.exists()
assert res_pth == target_pth
def test_prepare_save_path_FailNoTargets(tmp_path):
base_folder = tmp_path
dirs = None
filename = None
suffix = None
with pytest.raises(ValueError):
_ = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
)
def test_prepare_save_path_FailNoFilenameSuffix(tmp_path):
base_folder = tmp_path
dirs = None
filename = None
suffix = "pkl"
with pytest.raises(ValueError):
_ = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
)
filename = "test"
suffix = None
with pytest.raises(ValueError):
_ = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
)
def test_prepare_save_path_FailTimestampWithoutFilename(tmp_path):
base_folder = tmp_path
dirs = ["test"]
filename = None
suffix = None
with pytest.raises(ValueError):
_ = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
include_timestamp=True,
)
def test_prepare_save_path_FailBadSuffix(tmp_path):
base_folder = tmp_path
dirs = None
filename = "test"
suffix = "."
with pytest.raises(ValueError):
_ = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
include_timestamp=False,
)
def test_prepare_save_path_SuccessSuffixAddDot(tmp_path):
base_folder = tmp_path
dirs = None
filename = "test"
suffix = "pkl"
target_path = tmp_path / f"{filename}.{suffix}"
ret_path = paths.prepare_save_path(
base_folder,
dirs,
filename,
suffix,
create_folder=False,
include_timestamp=False,
)
assert ret_path == target_path
def test_search_cwd(monkeypatch, base_folder, target_file_pth):
monkeypatch.setattr(Path, "cwd", lambda: base_folder)
assert Path.cwd() == base_folder
ret = paths.search_cwd(FILE_SEARCH)
assert ret is None
target_folder = target_file_pth.parent
monkeypatch.setattr(Path, "cwd", lambda: target_folder)
assert Path.cwd() == target_folder
ret = paths.search_cwd(FILE_SEARCH)
assert ret is not None
assert ret == target_file_pth
@pytest.mark.parametrize("stop_folder_name", ["to", "base", None])
def test_search_file_iterative(base_folder, target_file_pth, stop_folder_name):
# target in parent of 'to': 'path'
ret = paths.search_file_iterative(base_folder, FILE_SEARCH, stop_folder_name)
if stop_folder_name == "to" or stop_folder_name is None:
assert ret is not None
assert ret.name == FILE_SEARCH
assert ret == target_file_pth
elif stop_folder_name == "base":
assert ret is None
def test_search_folder_path(base_folder):
stop_folder = "123" # should not exist
found = paths.search_folder_path(base_folder, stop_folder_name=stop_folder)
assert found is None
stop_folder = "to"
found = paths.search_folder_path(base_folder, stop_folder_name=stop_folder)
assert found is not None
assert found.name == "path"
stop_folder = None
found = paths.search_folder_path(base_folder, stop_folder_name=stop_folder)
assert found is None