diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..23b34cc Binary files /dev/null and b/.coverage differ diff --git a/.gitignore b/.gitignore index df67443..f41869b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ secrets/* !secrets/.gitkeep temp/ log/ -.vscode \ No newline at end of file + +.vscode +.coverage diff --git a/ChangeLog.md b/ChangeLog.md index e895f69..9290534 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,74 @@ # Updates +## 1.9.2 get to 90% update + +### new things + +- Suproting 100% + +- Utility 99% + - collor 100% + - deltatime 98% + - UnitConverter 100% + - config manager 100% + - logger 100% + - debug 97% + +- Data Prosesing 52% + - csv data pipeline 100% + - filter 96% + - map 91% + - PID 98% + - data swite will not be tested at this moment + +- contol softwere + - State machine + - will not be tested at this moment + +### bug Fix + +- somthing + +### changes + +- somthing + +### breaking changes + +- somthing + +## 1.9.1 location update + +### new things + +- Base Unit test +- 100% suporting + +### breaking changes + +- file structure + +## 1.9.0 Test update + +### new things + +- Unit test +- UnitTestUtilities.py + +### bug Fix + +- somthing + +### changes + +- yamlWriter now suports mode=a the right way +- Exsamlpe files now change name to exsample and not test +- all suporting script tests uptated to use the test util + +### breaking changes + +- Exsample is no longer the main way of testing the code + ## 1.8.2 csv update diff --git a/DataProsesing/DataRW.py b/DataProsesing/DataRW.py index 6bd7811..9733d5f 100644 --- a/DataProsesing/DataRW.py +++ b/DataProsesing/DataRW.py @@ -7,7 +7,7 @@ from ..Suporting.jsonWriter import * from ..Suporting.yamlWriter import * -#from ..Utility.Debug import Debug +from ..Utility.Debug import Debug def write_data(file_path,data, **kwargs): @@ -15,7 +15,7 @@ def write_data(file_path,data, **kwargs): directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) - #Debug.log(f"Dir did not exist, created it: {directory}", "Warning", group="WarningError") + Debug.log(f"Dir did not exist, created it: {directory}", "Warning", group="WarningError") ext = os.path.splitext(file_path)[1].lower() diff --git a/README.md b/README.md index 006539e..6476e67 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ using "import GabesPythonToolBox as GTB" for all or import "GabesPythonToolBox.Category.lib as (XX)" That’s it. You're good to go. +### comands +#### unit test +''' +pytest UnitTest -v +''' +#### get filestucture +''' +tree /F /A > folder_tree.txt +''' + ## Current Features ### ControllSoftwere diff --git a/Suporting/csvReader.py b/Suporting/csvReader.py index 3353fb0..97037b2 100644 --- a/Suporting/csvReader.py +++ b/Suporting/csvReader.py @@ -73,6 +73,9 @@ def read_csv(file_path, seperator_symbol: str = ',', float_symbol: str = '.', re # Process each line using the helper for line in lines: values = process_line(line, seperator_symbol, float_symbol) + if not values: + Debug.log(f"emty row","Info",group="LIB_Debug") + continue # skip empty lines row = {header[i]: values[i] for i in range(len(header))} Debug.log(f"row:\n{row}\n\n","Info",group="LIB_Debug") data.append(row) diff --git a/Suporting/csvWriterLogger.py b/Suporting/csvWriterLogger.py index 5cf744a..7572cfc 100644 --- a/Suporting/csvWriterLogger.py +++ b/Suporting/csvWriterLogger.py @@ -1,7 +1,7 @@ import copy import csv - +#TODO make it one file again #this is a direct coppy of csvWriter and oly exsists sice circular import isues def write_csv( diff --git a/Suporting/jsonWriter.py b/Suporting/jsonWriter.py index e34b084..0f19fbb 100644 --- a/Suporting/jsonWriter.py +++ b/Suporting/jsonWriter.py @@ -1,4 +1,27 @@ import json -def write_json(file_path,data,mode: str = 'w'): +import os + +def write_json(file_path, data, mode: str = 'w'): + """ + Write a dictionary to a JSON file. + If mode='a', merge with existing JSON content if it is a dict. + """ + if mode == 'a' and os.path.exists(file_path): + # Load existing data + with open(file_path, "r", encoding="utf-8") as f: + try: + existing_data = json.load(f) + except json.JSONDecodeError: + raise ValueError("Existing JSON content is invalid") + if not isinstance(existing_data, dict): + raise TypeError("Existing JSON content is not a dict; cannot merge") + + # Merge existing data with new data + existing_data.update(data) + data_to_write = existing_data + mode = 'w' # overwrite with merged content + else: + data_to_write = data + with open(file_path, mode=mode, encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) \ No newline at end of file + json.dump(data_to_write, f, indent=4, ensure_ascii=False) diff --git a/Suporting/yamlWriter.py b/Suporting/yamlWriter.py index 8a24c77..75803a8 100644 --- a/Suporting/yamlWriter.py +++ b/Suporting/yamlWriter.py @@ -1,4 +1,24 @@ import yaml -def write_yaml(file_path,data,mode: str = 'w'): - with open(file_path,mode=mode, encoding="utf-8") as f: - yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) \ No newline at end of file +import os + +def write_yaml(file_path, data, mode: str = 'w'): + """ + Write a dictionary to a YAML file. + If mode='a', merge with existing YAML content if it is a dict. + """ + if mode == 'a' and os.path.exists(file_path): + # Load existing data + with open(file_path, "r", encoding="utf-8") as f: + existing_data = yaml.safe_load(f) or {} + if not isinstance(existing_data, dict): + raise TypeError("Existing YAML content is not a dict; cannot merge") + + # Merge existing data with new data + existing_data.update(data) + data_to_write = existing_data + mode = 'w' # Overwrite file with merged content + else: + data_to_write = data + + with open(file_path, mode=mode, encoding="utf-8") as f: + yaml.safe_dump(data_to_write, f, sort_keys=False, allow_unicode=True) diff --git a/Tests/FallbackTest/.gitkeep b/Tests/FallbackTest/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Tests/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py new file mode 100644 index 0000000..10b2a69 --- /dev/null +++ b/Tests/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.ControllSoftwere.StateMachine.BaseState import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True diff --git a/Tests/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py new file mode 100644 index 0000000..1e31958 --- /dev/null +++ b/Tests/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.ControllSoftwere.StateMachine.NoneState import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True diff --git a/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py new file mode 100644 index 0000000..0b38918 --- /dev/null +++ b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.ControllSoftwere.StateMachine.StateLoader import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True diff --git a/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py new file mode 100644 index 0000000..cde38f4 --- /dev/null +++ b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.ControllSoftwere.StateMachine.StateManager import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True diff --git a/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py new file mode 100644 index 0000000..fd58cac --- /dev/null +++ b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.ControllSoftwere.StateMachine.StateSwitcher import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True diff --git a/Tests/UnitTest/DataProsesing/test_DataConsolPrinter.py b/Tests/UnitTest/DataProsesing/test_DataConsolPrinter.py new file mode 100644 index 0000000..3e4757d --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_DataConsolPrinter.py @@ -0,0 +1,9 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True + +#ingen ide hvordan teste disse \ No newline at end of file diff --git a/Tests/UnitTest/DataProsesing/test_DataFormater.py b/Tests/UnitTest/DataProsesing/test_DataFormater.py new file mode 100644 index 0000000..19d094d --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_DataFormater.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True \ No newline at end of file diff --git a/Tests/UnitTest/DataProsesing/test_DataHelperFunctions.py b/Tests/UnitTest/DataProsesing/test_DataHelperFunctions.py new file mode 100644 index 0000000..19d094d --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_DataHelperFunctions.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True \ No newline at end of file diff --git a/Tests/UnitTest/DataProsesing/test_DataPrettifyer.py b/Tests/UnitTest/DataProsesing/test_DataPrettifyer.py new file mode 100644 index 0000000..19d094d --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_DataPrettifyer.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True \ No newline at end of file diff --git a/Tests/UnitTest/DataProsesing/test_DataRW.py b/Tests/UnitTest/DataProsesing/test_DataRW.py new file mode 100644 index 0000000..19d094d --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_DataRW.py @@ -0,0 +1,7 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing +#TODO + +def test_doc(): + assert True \ No newline at end of file diff --git a/Tests/UnitTest/DataProsesing/test_PID.py b/Tests/UnitTest/DataProsesing/test_PID.py new file mode 100644 index 0000000..7a95c81 --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_PID.py @@ -0,0 +1,184 @@ +import pytest +from GabesPythonToolBox.DataProsesing.PID import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import almost_equal + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_proportional_only(Debug_enable): + pid = PID(P=2.0, I=0.0, D=0.0,Debug_enable=Debug_enable) + output, P_, I_, D_, error, dt = pid.Update(current_value=3, set_value=5, dt=1.0) + assert almost_equal(output, 4.0) + assert almost_equal(P_, 4.0) + assert almost_equal(I_, 0.0) + assert almost_equal(D_, 0.0) + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_integral_accumulation(Debug_enable): + pid = PID(P=0.0, I=1.0, D=0.0, Debug_enable=Debug_enable) + _, _, I1, _, _, _ = pid.Update(current_value=0, set_value=1, dt=1.0) + _, _, I2, _, _, _ = pid.Update(current_value=0, set_value=1, dt=1.0) + assert I2 > I1 # Integral should increase over time + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_derivative_response(Debug_enable): + pid = PID(P=0.0, I=0.0, D=1.0, Debug_enable=Debug_enable) + pid.Update(current_value=0, set_value=1, dt=1.0) # First call sets prev_error + output, _, _, D_, _, _ = pid.Update(current_value=0.5, set_value=1, dt=1.0) + # Derivative term should be negative (error decreasing) + assert D_ < 0 + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_integral_clamping(Debug_enable): + pid = PID(P=0.0, I=1.0, D=0.0, integral_limit=0.5, Debug_enable=Debug_enable) + for _ in range(10): + pid.Update(current_value=0, set_value=1, dt=1.0) + assert abs(pid.integral) <= 0.5 # Clamped integral + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_reset_integral(Debug_enable): + pid = PID(P=0.0, I=1.0, D=0.0, Debug_enable=Debug_enable) + pid.integral = 10 + pid.ResetIntegral() + assert pid.integral == 0 + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_change_parameters(Debug_enable): + pid = PID(P=1.0, I=1.0, D=1.0, Debug_enable=Debug_enable) + pid.Change(P_=2.0, I_=0.5, D_=0.1, integral_limit=5.0) + assert pid.P == 2.0 + assert pid.I == 0.5 + assert pid.D == 0.1 + assert pid.integral_limit == 5.0 + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_change_individual_terms(Debug_enable): + pid = PID(Debug_enable=Debug_enable) + pid.ChangeP(1.5) + pid.ChangeI(0.5) + pid.ChangeD(0.1) + assert pid.P == 1.5 + assert pid.I == 0.5 + assert pid.D == 0.1 + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_pid_factory_function_creates_pid(Debug_enable): + pid = NewPID(P=1, I=2, D=3, Debug_enable=Debug_enable) + assert isinstance(pid, PID) + assert pid.P == 1 + assert pid.I == 2 + assert pid.D == 3 + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_call_resets_integral_and_returns_tuple(Debug_enable): + pid = PID(P=0.0, I=2.0, D=0.0, Debug_enable=Debug_enable) + # accumulate some integral + pid.Update(current_value=0, set_value=1, dt=1.0) + pid.Update(current_value=0, set_value=1, dt=1.0) + assert pid.integral != 0 + + ret = pid.__call__(current_value=0, set_value=1, dt=1.0, resetIntegral=True) + # __call__ forwards to Update and should return the update tuple + assert isinstance(ret, tuple) + assert len(ret) == 6 + # ResetIntegral flag should zero the integral + assert pid.integral == 1 + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_call_matches_update_return(Debug_enable): + pid = PID(P=2.0, I=0.5, D=0.1, Debug_enable=Debug_enable) + expected = pid.Update(current_value=1.0, set_value=3.0, dt=0.5) + + # new instance to ensure __call__ invokes same logic as Update (no prior state) + pid2 = PID(P=2.0, I=0.5, D=0.1, Debug_enable=Debug_enable) + got = pid2.__call__(current_value=1.0, set_value=3.0, dt=0.5) + + assert len(got) == len(expected) == 6 + # compare numeric floats with tolerance, others by equality + for a, b in zip(expected, got): + if isinstance(a, float): + assert almost_equal(a, b) + else: + assert a == b + + +@pytest.mark.parametrize( + "Debug_enable", + [ + (False), + (True), + ], +) +def test_change_integral_limit_sets_value(Debug_enable): + pid = PID(Debug_enable=Debug_enable) + pid.Change_integral_limit(-2.5) + assert pid.integral_limit == -2.5 + + pid.Change_integral_limit(3.0) + assert pid.integral_limit == 3.0 diff --git a/Tests/UnitTest/DataProsesing/test_filter.py b/Tests/UnitTest/DataProsesing/test_filter.py new file mode 100644 index 0000000..9c8b43f --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_filter.py @@ -0,0 +1,77 @@ +import pytest +from GabesPythonToolBox.DataProsesing.filter import Filter, newFilter +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import almost_equal,Generate_sequence + + +# Tests + +def test_average_filter_basic(): + f = Filter(num_points=3, filter_type="average") + inputs = Generate_sequence(3) + outputs = [f(x) for x in inputs] + + # After all inputs, average should be (1 + 2 + 3) / 3 = 2 + assert pytest.approx(outputs[-1], 0.0001) == 2.0 + + +def test_average_filter_window_limit(): + f = Filter(num_points=3, filter_type="average") + # Add more than 3 points to ensure oldest values are dropped + val = Generate_sequence(5) + for x in val: + out = f(x) + # Only last 3 points (3,4,5) should remain, avg = 4 + assert almost_equal(out,4) + + +def test_new_weighted_filter(): + f = Filter(num_points=3, filter_type="newWeighted") + for val in [10, 20, 30]: + out = f(val) + # Weighted average (1*10 + 2*20 + 3*30) / 6 = 23.33... + assert almost_equal(out,23.3333333333) + + +def test_old_weighted_filter(): + f = Filter(num_points=3, filter_type="oldWeighted") + for val in [10, 20, 30]: + out = f(val) + # Weighted average (3*10 + 2*20 + 1*30) / 6 = 16.67... + assert almost_equal(out,16.666666666) + + +def test_invalid_input_type_raises(): + f = Filter(num_points=3) + with pytest.raises(TypeError): + f("invalid") # not numeric + + +def test_unsupported_filter_type_raises(): + f = Filter(num_points=3, filter_type="unknownType") + with pytest.raises(ValueError): + f(5) + + +def test_empty_filter_returns_zero(): + f = Filter(num_points=3) + # Manually clear data_points to simulate empty state + f.data_points = [] + result = f(1) # adds one, should average correctly + assert isinstance(result, float) + + +def test_window_behavior_pop(): + f = Filter(num_points=3) + f(1) + f(2) + f(3) + f(4) # should pop oldest + assert len(f.data_points) == 3 + assert f.data_points == [2, 3, 4] + + +def test_newFilter_factory_creates_instance(): + f = newFilter(5, "average") + assert isinstance(f, Filter) + assert f.num_points == 5 + assert f.filter_type == "average" diff --git a/Tests/UnitTest/DataProsesing/test_map.py b/Tests/UnitTest/DataProsesing/test_map.py new file mode 100644 index 0000000..4f5af66 --- /dev/null +++ b/Tests/UnitTest/DataProsesing/test_map.py @@ -0,0 +1,101 @@ +import pytest +import GabesPythonToolBox.DataProsesing.map as map +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import almost_equal + +# Map tests + +def test_map_basic_linear_mapping(): + # Simple case: map 5 from range 0–10 → 0–100 + result = map.Map(5, 0, 10, 0, 100) + assert almost_equal(result, 50) + + +def test_map_negative_to_positive_range(): + # Map -5 from -10–10 → 0–100 → should give 25 + result = map.Map(-5, -10, 10, 0, 100) + assert almost_equal(result, 25) + + +def test_map_reversed_input_range(): + # Reverse input range should still work correctly + result = map.Map(5, 10, 0, 0, 100) + assert almost_equal(result, 50) + + +def test_map_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10, 0, 100) + with pytest.raises(TypeError): + map.Map(0, "a", 10, 0, 100) + with pytest.raises(TypeError): + map.Map(10, 0, "a", 0, 100) + with pytest.raises(TypeError): + map.Map(0, 0, 10, "a", 100) + with pytest.raises(TypeError): + map.Map(100, 0, 10, 0, "a") + + + +# ClampMap tests + + +def test_clampmap_within_range(): + # Normal input: should act like Map + result = map.ClampMap(5, 0, 10, 0, 100) + assert almost_equal(result, 50) + + +def test_clampmap_above_max(): + # Input above maxInput should clamp to maxOutput + result = map.ClampMap(20, 0, 10, 0, 100) + assert almost_equal(result, 100) + + +def test_clampmap_below_min(): + # Input below minInput should clamp to minOutput + result = map.ClampMap(-10, 0, 10, 0, 100) + assert almost_equal(result, 0) + + +def test_clampmap_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10, 0, 100) + with pytest.raises(TypeError): + map.Map(0, "a", 10, 0, 100) + with pytest.raises(TypeError): + map.Map(10, 0, "a", 0, 100) + with pytest.raises(TypeError): + map.Map(0, 0, 10, "a", 100) + with pytest.raises(TypeError): + map.Map(100, 0, 10, 0, "a") + +# ClampMap tests + +def test_clamp_basic(): + assert map.Clamp(5, 0, 10) == 5 + assert map.Clamp(-1, 0, 10) == 0 + assert map.Clamp(15, 0, 10) == 10 + + +def test_clamp_min_only(): + assert map.Clamp(2, minOutput=5) == 5 + assert map.Clamp(10, minOutput=5) == 10 + + +def test_clamp_max_only(): + assert map.Clamp(12, maxOutput=10) == 10 + assert map.Clamp(3, maxOutput=10) == 3 + + +def test_clamp_no_limits(): + assert map.Clamp(7) == 7 + + +def test_clamp_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10) + with pytest.raises(TypeError): + map.Map(0, "a", 10) + with pytest.raises(TypeError): + map.Map(10, 0, "a") + \ No newline at end of file diff --git a/Tests/UnitTest/Suporting/test_csvReader.py b/Tests/UnitTest/Suporting/test_csvReader.py new file mode 100644 index 0000000..2e8d343 --- /dev/null +++ b/Tests/UnitTest/Suporting/test_csvReader.py @@ -0,0 +1,98 @@ +import pytest +from GabesPythonToolBox.Suporting.csvReader import read_csv, process_line +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import write_csv_for_test + +# Sample CSV content +csv_lines = [ + "name,age,score", + "Alice,30,95.5", + "Bob,25,88.2", + "Charlie,28,91.0", +] + +csv_lines_empty = [ + "name,age,score", + "Alice,30,95.5", + "", + "" +] + +# Tests +def test_process_line_basic(): + line = "Alice,30,95.5" + result = process_line(line) + assert result == ["Alice", "30", "95.5"] + +def test_process_line_empty_line(): + result = process_line(" ") + assert result == [] + +def test_process_line_custom_separators_and_floats(): + line = "Alice;30;95,5" + result = process_line(line, seperator_symbol=";", float_symbol=",") + assert result == ["Alice", "30", "95.5"] + +def test_read_csv_all_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file) + assert len(data) == 3 + assert data[0]["name"] == "Alice" + assert data[1]["score"] == "88.2" + +def test_read_csv_head_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file, read_from="head", read_n_lines=2) + assert len(data) == 2 + assert data[0]["name"] == "Alice" + +def test_read_csv_tail_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file, read_from="tail", read_n_lines=2) + assert len(data) == 2 + assert data[0]["name"] == "Bob" + assert data[1]["name"] == "Charlie" + +def test_read_csv_custom_separator_and_float(tmp_path): + test_file = tmp_path / "test.csv" + custom_lines = [ + "name;age;score", + "Alice;30;95,5", + "Bob;25;88,2" + ] + write_csv_for_test(test_file, custom_lines) + + data = read_csv(test_file, seperator_symbol=";", float_symbol=",") + assert data[0]["score"] == "95.5" + assert data[1]["score"] == "88.2" + +def test_read_csv_empty_file(tmp_path): + test_file = tmp_path / "empty.csv" + write_csv_for_test(test_file, []) + + # Should return empty list + data = read_csv(test_file) + assert data == [] + + +def test_read_csv_triggers_empty_line(tmp_path): + + test_file = tmp_path / "test.csv" + with open(test_file, "w", encoding="utf-8") as f: + for line in csv_lines_empty: + f.write(line + "\n") + + # Call read_csv with read_from="head" and read_n_lines=5 (more than actual lines) + data = read_csv(str(test_file), read_from="head", read_n_lines=5) + + + assert len(data) == 1 + assert data[0] == {"name": "Alice", "age": "30", "score": "95.5"} + + empty_line_values = process_line("") + assert empty_line_values == [] \ No newline at end of file diff --git a/Tests/UnitTest/Suporting/test_csvWriter.py b/Tests/UnitTest/Suporting/test_csvWriter.py new file mode 100644 index 0000000..e588385 --- /dev/null +++ b/Tests/UnitTest/Suporting/test_csvWriter.py @@ -0,0 +1,73 @@ +import pytest +from GabesPythonToolBox.Suporting.csvWriter import write_csv +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import read_csv_test,sample_data,write_json_test + +# Tests +def test_write_csv_creates_file(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data) + assert test_file.exists() + +def test_write_csv_exception_handling(tmp_path): + + invalid_file_path = tmp_path / "some_dir" + invalid_file_path.mkdir() + + with pytest.raises(Exception) as exc_info: + write_csv(invalid_file_path, sample_data) + + assert "some_dir" in str(exc_info.value) + +def test_write_csv_headers_and_body(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data, data_mode="all") + loaded = read_csv_test(test_file) + # Check first row content + assert loaded[0]["name"] == "Alice" + assert loaded[1]["score"] == "88.2" # default float symbol + +def test_write_csv_float_symbol(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data, float_symbol=",") + loaded = read_csv_test(test_file) + # Floats should have ',' instead of '.' + assert loaded[0]["score"] == "95,5" + assert loaded[1]["score"] == "88,2" + +def test_write_csv_data_modes(tmp_path): + test_file_all = tmp_path / "all.csv" + test_file_head = tmp_path / "head.csv" + test_file_body = tmp_path / "body.csv" + test_file_none = tmp_path / "none.csv" + + write_csv(test_file_all, sample_data, data_mode="all") + write_csv(test_file_head, sample_data, data_mode="head") + write_csv(test_file_body, sample_data, data_mode="body") + write_csv(test_file_none, sample_data, data_mode="none") + + # all: header + body + loaded_all = read_csv_test(test_file_all) + assert len(loaded_all) == 2 + # head: no body + with open(test_file_head, encoding="utf-8") as f: + content_head = f.read() + assert "Alice" not in content_head + # body: no header + with open(test_file_body, encoding="utf-8") as f: + content_body = f.read() + assert "name,age,score" not in content_body + # none: nothing written + with open(test_file_none, encoding="utf-8") as f: + content_none = f.read() + assert content_none.strip() == "" + +#TODO write UTF8 data + +def test_write_csv_invalid_data(tmp_path): + test_file = tmp_path / "test.csv" + with pytest.raises(ValueError): + write_csv(test_file, {"not": "a list"}) + with pytest.raises(ValueError): + write_csv(test_file, []) + with pytest.raises(ValueError): + write_csv(test_file, [{"valid": 1}, "invalid row"]) diff --git a/Tests/UnitTest/Suporting/test_jsonReader.py b/Tests/UnitTest/Suporting/test_jsonReader.py new file mode 100644 index 0000000..9bebc98 --- /dev/null +++ b/Tests/UnitTest/Suporting/test_jsonReader.py @@ -0,0 +1,33 @@ +import pytest +from GabesPythonToolBox.Suporting.jsonReader import read_json +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import write_json_test,sample_data,Complex_data + +import json + +# Tests +def test_read_json_basic(tmp_path): + test_file = tmp_path / "test.json" + write_json_test(test_file, sample_data) + loaded = read_json(test_file) + assert loaded == sample_data + +def test_read_json_with_unicode(tmp_path): + test_file = tmp_path / "test.json" + write_json_test(test_file, Complex_data) + + loaded = read_json(test_file, encoding="utf-8") + assert loaded == Complex_data + +def test_read_json_nonexistent_file_raises(tmp_path): + test_file = tmp_path / "missing.json" + + with pytest.raises(FileNotFoundError): + read_json(test_file) + +def test_read_json_invalid_json_raises(tmp_path): + test_file = tmp_path / "invalid.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write("invalid json content") + + with pytest.raises(json.JSONDecodeError): + read_json(test_file) diff --git a/Tests/UnitTest/Suporting/test_jsonWriter.py b/Tests/UnitTest/Suporting/test_jsonWriter.py new file mode 100644 index 0000000..6ab6253 --- /dev/null +++ b/Tests/UnitTest/Suporting/test_jsonWriter.py @@ -0,0 +1,51 @@ +import pytest +from GabesPythonToolBox.Suporting.jsonWriter import write_json +import json +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import read_json_test,sample_data,Complex_data,New_data + +# Tests +def test_write_json_creates_file(tmp_path): + test_file = tmp_path / "test.json" + data = {"key": "value"} + write_json(test_file, sample_data) + assert test_file.exists() + assert read_json_test(test_file) == sample_data + +def test_write_json_overwrites_file(tmp_path): + test_file = tmp_path / "test.json" + write_json(test_file, sample_data) + write_json(test_file, New_data) + assert read_json_test(test_file) == New_data + +def test_write_json_merge_append(tmp_path): + test_file = tmp_path / "test.json" + data0=sample_data[0] + data1=sample_data[1] + write_json(test_file,data0) + write_json(test_file, data1, mode='a') # merge dictionaries + expected = data0.copy() + expected.update(data1) + assert read_json_test(test_file) == expected + +def test_write_json_append_non_dict_raises(tmp_path): + test_file = tmp_path / "test.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write(json.dumps(["not", "a", "dict"])) + + with pytest.raises(TypeError): + write_json(test_file, {"new": "data"}, mode='a') + +def test_write_json_append_invalid_json_raises(tmp_path): + test_file = tmp_path / "test.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write("invalid json") + + with pytest.raises(ValueError): + write_json(test_file, {"new": "data"}, mode='a') + +def test_write_json_unicode_support(tmp_path): + test_file = tmp_path / "test.json" + data = Complex_data + write_json(test_file, data) + loaded = read_json_test(test_file) + assert loaded == data diff --git a/Tests/UnitTest/Suporting/test_yamlReader.py b/Tests/UnitTest/Suporting/test_yamlReader.py new file mode 100644 index 0000000..23835fb --- /dev/null +++ b/Tests/UnitTest/Suporting/test_yamlReader.py @@ -0,0 +1,27 @@ +import pytest +from GabesPythonToolBox.Suporting.yamlReader import read_ymal +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import write_yaml_test,sample_data,Complex_data + +# Supporting func to help the test + + +# Tests +def test_read_ymal_reads_correctly(tmp_path): + test_file = tmp_path / "test.yaml" + write_yaml_test(test_file, sample_data) + + loaded = read_ymal(test_file) + assert loaded == sample_data + +def test_read_ymal_with_different_encoding(tmp_path): + test_file = tmp_path / "test.yaml" + write_yaml_test(test_file, Complex_data) + + loaded = read_ymal(test_file, encoding="utf-8") + assert loaded == Complex_data + +def test_read_ymal_nonexistent_file_raises(tmp_path): + test_file = tmp_path / "missing.yaml" + + with pytest.raises(FileNotFoundError): + read_ymal(test_file) diff --git a/Tests/UnitTest/Suporting/test_yamlWriter.py b/Tests/UnitTest/Suporting/test_yamlWriter.py new file mode 100644 index 0000000..f59a28a --- /dev/null +++ b/Tests/UnitTest/Suporting/test_yamlWriter.py @@ -0,0 +1,46 @@ +import pytest +from GabesPythonToolBox.Suporting.yamlWriter import write_yaml +import os +import yaml +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import read_yaml_test,sample_data,Complex_data,New_data + +# Tests +def test_write_yaml_creates_file(tmp_path): + test_file = tmp_path / "test.yaml" + write_yaml(test_file, sample_data) + + assert test_file.exists() + assert read_yaml_test(test_file) == sample_data + +def test_write_yaml_overwrites_file(tmp_path): + test_file = tmp_path / "test.yaml" + write_yaml(test_file, sample_data) + write_yaml(test_file, New_data) + + assert read_yaml_test(test_file) == New_data + +def test_write_yaml_merge_append(tmp_path): + test_file = tmp_path / "test.yaml" + + data0=sample_data[0] + data1=sample_data[1] + write_yaml(test_file, data0) + + # New data to append (merge) + write_yaml(test_file, data1, mode='a') + + expected = data0.copy() + expected.update(data1) + assert read_yaml_test(test_file) == expected + + +def test_write_yaml_append_non_dict_raises(tmp_path): + test_file = tmp_path / "test.yaml" + + # Write non-dict content + with open(test_file, "w", encoding="utf-8") as f: + f.write("Just a string") + + # Trying to append dict should raise TypeError + with pytest.raises(TypeError): + write_yaml(test_file, {"new": "data"}, mode='a') diff --git a/Tests/UnitTest/Utility/test_ConfigManager.py b/Tests/UnitTest/Utility/test_ConfigManager.py new file mode 100644 index 0000000..8fa1f04 --- /dev/null +++ b/Tests/UnitTest/Utility/test_ConfigManager.py @@ -0,0 +1,186 @@ +import pytest +import copy +import GabesPythonToolBox.Utility.ConfigManager as CM +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import ( + read_json_test, + read_yaml_test, + write_config_test, + sample_config, +) + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_load_and_call(tmp_path, ext): + file = tmp_path / f"lab_config{ext}" + write_config_test(file, sample_config) + + cm = CM.ConfigManager(str(file)) + config = cm() + assert config["university"]["name"] == "GTB University" + assert config["students"][0]["name"] == "Alice" + assert isinstance(config, dict) + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_update_nested_and_indexed_keys(tmp_path, ext): + file = tmp_path / f"update_test{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + # Update simple nested key + cm.update("university.department", "Artificial Intelligence") + assert cm.config["university"]["department"] == "Artificial Intelligence" + + # Update list item by index + cm.update("students[1].project", "AI Ethics Research") + assert cm.config["students"][1]["project"] == "AI Ethics Research" + + # Update deeply nested dictionary key + cm.update("lab_settings.equipment.3D_printers", 3) + assert cm.config["lab_settings"]["equipment"]["3D_printers"] == 3 + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_save_and_reload_same_format(tmp_path, ext): + file = tmp_path / f"save_test{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.update("projects.active[0].progress", 90) + new_file = tmp_path / f"saved{ext}" + cm.save(str(new_file)) + assert new_file.exists() + + reloaded = CM.ConfigManager(str(new_file))() + assert reloaded["projects"]["active"][0]["progress"] == 90 + + +@pytest.mark.parametrize("ext_from, ext_to", [ + (".json", ".yaml"), + (".yaml", ".json"), + (".yml", ".json"), +]) +def test_cross_format_save(tmp_path, ext_from, ext_to): + file = tmp_path / f"cross{ext_from}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.update("university.name", "Techville Institute of Robotics") + out_file = tmp_path / f"converted{ext_to}" + cm.save(str(out_file)) + + assert out_file.exists() + loaded = read_json_test(out_file) if ext_to == ".json" else read_yaml_test(out_file) + assert loaded["university"]["name"] == "Techville Institute of Robotics" + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_reset_to_original(tmp_path, ext): + file = tmp_path / f"reset_test{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.update("students[0].name", "Charlie") + assert cm.config["students"][0]["name"] == "Charlie" + + cm.reset() + assert cm.config["students"][0]["name"] == "Alice" + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_start_config_manager_factory(tmp_path, ext): + file = tmp_path / f"factory_test{ext}" + write_config_test(file, sample_config) + + cm = CM.startConfigManager(str(file)) + assert isinstance(cm, CM.ConfigManager) + assert cm()["projects"]["active"][1]["title"] == "Drone Swarm Control" + +def test_update_no_config(): + # ensure __call__'s "no Config Loaded" path runs + cm = CM.ConfigManager() # no path -> config stays None + assert cm() is None + +def test_missing_file_raises_explicit(tmp_path): + missing = tmp_path / "definitely_missing_config.json" + with pytest.raises(FileNotFoundError): + CM.ConfigManager(str(missing)) # pass string so loadConfig is invoked + + +def test_invalid_extension_raises(tmp_path): + bad_file = tmp_path / "config.txt" + bad_file.write_text("not a valid format", encoding="utf-8") + with pytest.raises(ValueError): + CM.ConfigManager(str(bad_file)) + + +def test_invalid_json_content_raises(tmp_path): + bad_file = tmp_path / "broken.json" + bad_file.write_text("{ bad json data", encoding="utf-8") + with pytest.raises(Exception): + CM.ConfigManager(str(bad_file)) + + +def test_init_without_path_and_call_returns_none(): + cm = CM.ConfigManager() # no path provided + assert cm.config is None + assert cm() is None + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_default_config_is_deepcopy_and_independent(tmp_path, ext): + file = tmp_path / f"deepcopy_test{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + cm.config["students"][0]["name"] = "ChangedName" + assert cm.defaultConfig["students"][0]["name"] == "Alice" + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_add_merges_dict_and_ignores_non_dict(tmp_path, ext): + file = tmp_path / f"add_test{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.add({"new_section": {"a": 1}}) + assert "new_section" in cm.config + assert cm.config["new_section"]["a"] == 1 + + before = copy.deepcopy(cm.config) + cm.add(["not", "a", "dict"]) + assert cm.config == before + +def test_update_nonexistent_key_raises(tmp_path): + file = tmp_path / "noexist.json" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + with pytest.raises(Exception): + cm.update("this.key.does.not.exist", 123) + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_save_with_empty_path_returns_without_writing(tmp_path, ext): + file = tmp_path / f"saveempty{ext}" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.save("") + reloaded = CM.ConfigManager(str(file))() + assert reloaded["university"]["name"] == sample_config["university"]["name"] + +def test_update_top_level_key_updates(tmp_path): + file = tmp_path / "toplevel_update.json" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + cm.update("university", {"name": "Techville Institute"}) + assert cm.config["university"]["name"] == "Techville Institute" + +def test_update_final_key_index_replaces_list_item(tmp_path): + file = tmp_path / "update_final_index.json" + write_config_test(file, sample_config) + cm = CM.ConfigManager(str(file)) + + new_student = {"name": "Zoe", "project": "Quantum UI"} + cm.update("students[0]", new_student) + + assert cm.config["students"][0]["name"] == "Zoe" + assert cm.config["students"][0]["project"] == "Quantum UI" \ No newline at end of file diff --git a/Tests/UnitTest/Utility/test_Debug.py b/Tests/UnitTest/Utility/test_Debug.py new file mode 100644 index 0000000..bda237c --- /dev/null +++ b/Tests/UnitTest/Utility/test_Debug.py @@ -0,0 +1,167 @@ +import os +import re +from types import SimpleNamespace +import pytest + +from GabesPythonToolBox.Utility.Debug import Debug, LogType, LogGroup +from GabesPythonToolBox.Utility import Logger + + +def reset_debug(monkeypatch): + monkeypatch.setattr(Debug, "debug_enabled", True) + monkeypatch.setattr(Debug, "logger_enabled", False) + monkeypatch.setattr(Debug, "verbosity", 1) + monkeypatch.setattr(Debug, "groups", { + LogGroup.LIB.value: False, + LogGroup.LIB_Debug.value: False, + LogGroup.ExampleFiles.value: False, + LogGroup.WarningError.value: True, + LogGroup.Showcase.value: True + }) + + +@pytest.mark.parametrize( + "message_type,text", + [ + ("Header", "Custom Header Message"), + ("Error", "This is an error message"), + ("Fail", "This is a failed message"), + ("Unachievable", "This is a Unachievable message"), + ("Success", "This is a success message"), + ("Warning", "This is a warning message"), + ("Info", "This is an info message"), + ("InProgress", "This is an in-progress message"), + ("None", "This is an None Format message"), + ("End", "This is an End message"), + ("-", "This has a wrong type"), + ], +) +def test_string_message_types_print(monkeypatch, capsys, message_type, text): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=1) + Debug.log(text, message_type=message_type) + out = capsys.readouterr().out + assert text in out + + +def test_enum_and_old_style_message_types(monkeypatch, capsys): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=1) + + Debug.log("New style message", message_type=LogType.Success) + assert "New style message" in capsys.readouterr().out + + Debug.log("Old style message", message_type="Success") + assert "Old style message" in capsys.readouterr().out + + +def test_group_filtering_and_toggle(monkeypatch, capsys): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=1) + + Debug.add_group("LIB", True) + Debug.add_group("Database", False) + Debug.add_group("Showcase", True) + + # LIB enabled -> prints + Debug.log("show lib", message_type="Info", group="LIB") + assert "show lib" in capsys.readouterr().out + + # Database disabled -> suppressed + Debug.log("hide db", message_type="Info", group="Database") + assert "hide db" not in capsys.readouterr().out + + # enable Database -> prints + Debug.enable_group("Database") + Debug.log("show db", message_type="Info", group="Database") + assert "show db" in capsys.readouterr().out + + # disable LIB -> suppressed, then re-enable -> prints + Debug.disable_group("LIB") + Debug.log("now hide lib", message_type="Info", group="LIB") + assert "now hide lib" not in capsys.readouterr().out + + Debug.enable_group("LIB") + Debug.log("now show lib", message_type="Info", group="LIB") + assert "now show lib" in capsys.readouterr().out + + +def test_list_groups_add_remove_affects_listing(monkeypatch, capsys): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=1) + + Debug.log("list groups", message_type="Info", group="Showcase") + Debug.log(Debug.list_groups(), message_type="Info", group="Showcase") + out = capsys.readouterr().out + assert LogGroup.Showcase.value in out or LogGroup.WarningError.value in out + + Debug.add_group("test") + Debug.log(Debug.list_groups(), message_type="Info", group="Showcase") + out = capsys.readouterr().out + assert "'test'" in out or '"test"' in out + + Debug.remove_group("test") + Debug.log(Debug.list_groups(), message_type="Info", group="Showcase") + out = capsys.readouterr().out + assert "'test'" not in out and '"test"' not in out + + +def test_set_log_enabled_uses_Logger(monkeypatch): + reset_debug(monkeypatch) + calls = [] + + def fake_start(cls, path=None): + calls.append(("start", path)) + + def fake_log(cls, message, message_type=None, group=None, verbose=None): + calls.append(("log", message, message_type, group, hasattr(verbose, "filename"))) + + # patch the Logger class methods on the module's Logger class object + monkeypatch.setattr(Logger.Logger, "start_logger", classmethod(fake_start)) + monkeypatch.setattr(Logger.Logger, "log", classmethod(fake_log)) + + # call twice to exercise both enabled=False and enabled=True paths + Debug.set_log_enabled(path="somepath", enabled=False) + Debug.set_log_enabled(path="somepath", enabled=True) + assert Debug.logger_enabled is True + + Debug.log("hello logger", message_type=LogType.Info, group=LogGroup.LIB) + assert any(c[0] == "log" and c[1] == "hello logger" for c in calls) + + +@pytest.mark.parametrize( + "verbosity, expect_file, expect_func, expect_lineno", + [ + (1, False, False, False), + (2, True, False, False), + (3, True, True, False), + (4, True, True, True), + ], +) +def test_verbosity_levels(monkeypatch, capsys, verbosity, expect_file, expect_func, expect_lineno): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=verbosity) + Debug.log("vtest", message_type=LogType.Info) + out = capsys.readouterr().out + assert "vtest" in out + if expect_file: + assert os.path.basename(__file__) in out + else: + assert os.path.basename(__file__) not in out + if expect_func: + assert "test_verbosity_levels" in out + if expect_lineno: + # check there's a colon + digits somewhere near the end (simple heuristic) + assert re.search(r":\s*\d+", out) + + +def test_unknown_message_type_default_branch_shows_file_and_marker(monkeypatch, capsys): + reset_debug(monkeypatch) + Debug.set_debug_enabled(True, verbosity=4) + Debug.log("fallback", message_type="SOMETHING_UNKNOWN") + out = capsys.readouterr().out + assert "fallback" in out + assert os.path.basename(__file__) in out + # example code prints a "..." marker when building fallback verbose info + assert "..." in out + diff --git a/Tests/UnitTest/Utility/test_DeltaTime.py b/Tests/UnitTest/Utility/test_DeltaTime.py new file mode 100644 index 0000000..475207b --- /dev/null +++ b/Tests/UnitTest/Utility/test_DeltaTime.py @@ -0,0 +1,81 @@ +import pytest +from GabesPythonToolBox.Utility.DeltaTime import DeltaTime, DeltaTimer,StartDeltaTimer +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import almost_equal +import time + +#DeltaTime tests + +def test_deltatime_basic(): + dt = DeltaTime() + time.sleep(0.01) # small sleep + delta = dt() + assert delta > 0 + # consecutive call should give a smaller delta if called immediately + delta2 = dt() + assert almost_equal(delta2, 0, tol=.1e-2) # allow 0.1s tolerance + +#DeltaTimer tests + + +def test_deltatimer_start_and_update(): + timer = StartDeltaTimer(0.05) # 50ms timer + # Initially not started + assert timer() is False + assert not timer.Finished + # Start timer + timer.startTimer() + time.sleep(0.02) + finished = timer() + assert finished is False + # timeLeft should decrease + assert timer.timeLeft < timer.duration + # chek finished + time.sleep(0.04) + finished = timer() + assert finished is True + +def test_deltatimer_finish(): + timer = DeltaTimer(0.03) + timer.startTimer() + time.sleep(0.05) + finished = timer() + assert finished is True + assert timer.Finished + assert timer.timeLeft <= 0 + +def test_deltatimer_pause_and_continue(): + timer = DeltaTimer(0.05) + timer.startTimer() + time.sleep(0.02) + timer.pauseTimer() + paused_time_left = timer.getTimeLeft() + time.sleep(0.02) + # Timer should not decrease while paused + assert almost_equal(paused_time_left, timer.getTimeLeft(), tol=1e-3) + timer.continueTimer() + time.sleep(0.02) + # After continuing, timer should decrease + assert timer.getTimeLeft() < paused_time_left + +def test_deltatimer_stop(): + timer = DeltaTimer(0.05) + timer.startTimer() + timer.stopTimer() + assert timer.Finished + assert timer.timeLeft == timer.timeLeft # no change after stop + finished = timer() + assert finished is True + + +def test_deltatimer_recurrent_start_finish(): + #Start timer, let it finish, start again, and finish again (simulate recurring events). + timer = DeltaTimer(00.03) + test_runs = 6 + i=0 + + while i < test_runs: + timer.startTimer() + time.sleep(0.05) + assert timer() is True + assert timer.Finished + i+=1 diff --git a/Tests/UnitTest/Utility/test_Logger.py b/Tests/UnitTest/Utility/test_Logger.py new file mode 100644 index 0000000..d3c1334 --- /dev/null +++ b/Tests/UnitTest/Utility/test_Logger.py @@ -0,0 +1,115 @@ +import os +import csv +from types import ModuleType, SimpleNamespace +from datetime import datetime +import sys +import pytest + +from GabesPythonToolBox.Utility.Logger import Logger + + +def reset_logger_state(): + Logger.path = None + Logger.encoding = None + Logger.first_write = True + + +def test_before_start_raises(): + reset_logger_state() + with pytest.raises(RuntimeError): + Logger.log("no start") + + +def test_default_folder(tmp_path, monkeypatch): + reset_logger_state() + # run in tmp_path so default "log/" is created there + monkeypatch.chdir(tmp_path) + Logger.start_logger(file_path="", character_encoding="utf-8") + + assert Logger.path is not None + # path should be inside tmp_path / "log_*.csv" + assert str(tmp_path) in Logger.path + assert os.path.exists(Logger.path) + with open(Logger.path, newline="", encoding="utf-8") as f: + reader = csv.reader(f) + assert next(reader) == Logger.headers + + +def test_existing_file_kept(tmp_path): + reset_logger_state() + file_path = tmp_path / "existing.csv" + file_path.write_text("PREEXISTING\n", encoding="utf-8") + + # start_logger should respect existing file and not rewrite header + Logger.start_logger(file_path=str(file_path), character_encoding="utf-8") + assert Logger.path == os.path.abspath(str(file_path)) + content = open(Logger.path, encoding="utf-8").read() + assert "PREEXISTING" in content + + +def test_dir_only_creates_file(tmp_path): + reset_logger_state() + dir_path = str(tmp_path / "logs") + os.sep + Logger.start_logger(file_path=dir_path, character_encoding="utf-8") + + assert Logger.path is not None + assert os.path.isdir(os.path.dirname(Logger.path)) + assert os.path.exists(Logger.path) + with open(Logger.path, newline="", encoding="utf-8") as f: + reader = csv.reader(f) + assert next(reader) == Logger.headers + + +def test_save_modes_and_log(tmp_path, monkeypatch): + reset_logger_state() + logfile = str(tmp_path / "log.csv") + Logger.start_logger(file_path=logfile) + + module_name = "GabesPythonToolBox.Suporting.csvWriterLogger" + mod = ModuleType(module_name) + calls = [] + + def fake_write_csv(path, data, mode, data_mode): + calls.append({"path": path, "data": data, "mode": mode, "data_mode": data_mode}) + + mod.write_csv = fake_write_csv + monkeypatch.setitem(sys.modules, module_name, mod) + + # Current state after start_logger: Logger.first_write == False -> save_to_file should treat as first write (mode "w", data_mode "all") + sample_data1 = [{"message": "first"}] + Logger.save_to_file(sample_data1) + assert calls[-1]["path"] == Logger.path + assert calls[-1]["mode"] == "w" + assert calls[-1]["data_mode"] == "all" + assert calls[-1]["data"] == sample_data1 + # save_to_file sets Logger.first_write = True + assert Logger.first_write is True + + # Force the alternate branch by setting first_write True before calling save_to_file + Logger.first_write = True + sample_data2 = [{"message": "second"}] + Logger.save_to_file(sample_data2) + # now first_write was True -> mode "a","body" + assert calls[-1]["mode"] == "a" + assert calls[-1]["data_mode"] == "body" + assert calls[-1]["data"] == sample_data2 + + # Test Logger.log building an entry (verbose=None) + Logger.log("hello", message_type="INFO", group="grp", verbose=None) + last = calls[-1] + entry = last["data"][0] + assert entry["message"] == "hello" + assert entry["message_type"] == "INFO" + assert entry["group"] == "grp" + assert entry["filename"] == "" + assert entry["filepath"] == "" + assert entry["lineno"] == 0 + datetime.fromisoformat(entry["timestamp"]) # should not raise + + # Test Logger.log with a verbose object containing filename and lineno + verbose = SimpleNamespace(filename=str(tmp_path / "sub" / "file.py"), lineno=42) + Logger.log("v", verbose=verbose) + entry2 = calls[-1]["data"][0] + assert entry2["filename"] == os.path.basename(verbose.filename) + assert entry2["filepath"] == os.path.dirname(verbose.filename) + assert entry2["lineno"] == 42 \ No newline at end of file diff --git a/Tests/UnitTest/Utility/test_UnitConverter.py b/Tests/UnitTest/Utility/test_UnitConverter.py new file mode 100644 index 0000000..e498fb8 --- /dev/null +++ b/Tests/UnitTest/Utility/test_UnitConverter.py @@ -0,0 +1,86 @@ +import json +import pytest +from GabesPythonToolBox.Utility.UnitConverter import * +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import Nothing, read_config +#TODO + +config = '''{"time": { + "s": {"coefficient": 1}, + "min": {"coefficient": 60}, + "hr": {"coefficient": 3600}, + "day": {"coefficient": 86400}, + "week": {"coefficient": 604800} +}, +"temperature": { + "K": {"coefficient": 1, "offset": 0}, + "C": {"coefficient": 1, "offset": -273.15}, + "F": {"coefficient": 1.8, "offset": -459.67} +}}''' + +def test_doc(): + UC = UnitsConverter(read_config(config)) + assert True + +def test_init(): + UC = UnitsConverter(read_config(config)) + assert UC.config == read_config(config) + +def test_convert_simple(): + UC = UnitsConverter(read_config(config)) + test = 60 + con = UC.convert(test, 's', 'min', 'time') + assert con == 1 #cheks from base to nr + + test = 1 + con = UC.convert(test, 'min', 's', 'time') + assert con == 60 #cheks from nr to base + + +def test_convert_scaler(): + UC = UnitsConverter(read_config(config)) + test = 60 + con = UC.convert(test, 'min', 'hr', 'time') + assert con == 1 + + +def test_init_with_dict(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + assert UC.config == cfg + +def test_convert_simple_with_dict(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + assert UC.convert(60, 's', 'min', 'time') == pytest.approx(1) + assert UC.convert(1, 'min', 's', 'time') == pytest.approx(60) + +def test_convert_scaler_with_dict(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + assert UC.convert(60, 'min', 'hr', 'time') == pytest.approx(1) + +def test_temperature_conversions(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + # Celsius -> Kelvin and back + assert UC.convert(0, 'C', 'K', 'temperature') == pytest.approx(273.15) + assert UC.convert(273.15, 'K', 'C', 'temperature') == pytest.approx(0) + # Fahrenheit -> Celsius (32°F == 0°C) + assert UC.convert(32, 'F', 'C', 'temperature') == pytest.approx(0) + +def test_invalid_dimension_and_unit_errors(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + with pytest.raises(ValueError): + UC.convert(1, 'm', 'km', 'length') # 'length' not present in provided config string + with pytest.raises(ValueError): + UC.convert(1, 's', 'unknown_unit', 'time') + +def test_NewConversionTable_and_usage(): + cfg = read_config(config) + UC = UnitsConverter(cfg) + new_cfg = {"length": {"m": {"coefficient": 1}, "km": {"coefficient": 1000}}} + UC.NewConversionTable(new_cfg) + assert UC.config == new_cfg + # 1000 m -> 1 km using stored coefficients + assert UC.convert(1000, 'm', 'km', 'length') == pytest.approx(1) \ No newline at end of file diff --git a/Tests/UnitTestComon/UntTestUtility.py b/Tests/UnitTestComon/UntTestUtility.py new file mode 100644 index 0000000..6a9883d --- /dev/null +++ b/Tests/UnitTestComon/UntTestUtility.py @@ -0,0 +1,107 @@ +import csv +import json +import yaml + +import os + +def read_config(cfg_str): + """ + Parse JSON string `cfg_str` and return a dict. + Bruk: read_config(config) eller read_config(confi2) + """ + return json.loads(cfg_str) + +def Nothing(): + ... + +def almost_equal(a, b, tol=1e-6): + return abs(a - b) < tol + +def Generate_sequence(n): + """Generate a simple increasing numeric sequence.""" + return [float(i + 1) for i in range(n)] + +def write_csv_for_test(file_path, lines): + """Helper function to write raw CSV lines for testing.""" + with open(file_path, "w", encoding="utf-8") as f: + for line in lines: + f.write(line + "\n") + +def read_csv_test(file_path, delimiter=','): + """Helper to read CSV back into list-of-dicts for testing.""" + with open(file_path, newline="", encoding="utf-8") as f: + reader = csv.DictReader(f, delimiter=delimiter) + return list(reader) + +def write_json_test(file_path, data): + """Helper function to create JSON files for testing read_json.""" + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + +def read_json_test(file_path): + with open(file_path, "r", encoding="utf-8") as f: + return json.load(f) + +def write_yaml_test(file_path, data): + """Helper to write YAML files for testing read function.""" + with open(file_path, "w", encoding="utf-8") as f: + yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + +def read_yaml_test(file_path): + """Helper function to read YAML for test verification.""" + with open(file_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + +def write_config_test(file_path, data): + """Write a JSON or YAML file for testing.""" + ext = os.path.splitext(file_path)[1].lower() + + with open(file_path, "w", encoding="utf-8") as f: + if ext == ".json": + write_json_test(file_path, data) + elif ext in [".yaml", ".yml"]: + write_yaml_test(file_path, data) + else: + raise ValueError("Unsupported test config type") + +# Sample test data +sample_data = [ + {"name": "Alice", "age": 30, "score": 95.5}, + {"name": "Bob", "age": 25, "score": 88.2}, +] + +# Complex test data +Complex_data = [ + {"greeting": "こんにちは", "key": "üñîçødë"}, +] + +New_data = [ + {"name": "Jacob", "age": 23, "score": 50.9}, + {"name": "Tory", "age": 21, "score": 99.8}, +] + +sample_config = { + "university": { + "name": "GTB University", + "department": "Robotics Engineering" + }, + "students": [ + {"id": 1, "name": "Alice", "project": "Autonomous Rover"}, + {"id": 2, "name": "Bob", "project": "Drone Swarm Control"} + ], + "projects": { + "active": [ + {"title": "Autonomous Rover", "progress": 65, "supervisor": "Dr. Grey"}, + {"title": "Drone Swarm Control", "progress": 40, "supervisor": "Dr. Lin"} + ], + "completed": [] + }, + "lab_settings": { + "open_hours": "08:00-18:00", + "max_students": 10, + "equipment": { + "3D_printers": 2, + "robots": ["Atlas", "Spot", "Rover"] + } + } +} \ No newline at end of file diff --git a/Tests/UnitTestComon/test_UntTestUtility.py b/Tests/UnitTestComon/test_UntTestUtility.py new file mode 100644 index 0000000..3058a1d --- /dev/null +++ b/Tests/UnitTestComon/test_UntTestUtility.py @@ -0,0 +1,73 @@ +import pytest +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import * + +import pytest +from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import ( + Nothing, + almost_equal, + Generate_sequence, + write_csv_for_test, + read_csv_test, + write_json_test, + read_json_test, + write_yaml_test, + read_yaml_test, + write_config_test +) + +# --- Tests --- + +def test_Nothing(): + # Should simply run without errors and return None + assert Nothing() is None + +def test_almost_equal(): + assert almost_equal(1.0000001, 1.0) + assert not almost_equal(1.001, 1.0) + +def test_Generate_sequence(): + seq = Generate_sequence(5) + assert seq == [1.0, 2.0, 3.0, 4.0, 5.0] + # Ensure floats + assert all(isinstance(x, float) for x in seq) + +@pytest.mark.parametrize("lines", [ + (["a,b,c", "1,2,3", "4,5,6"]) +]) +def test_csv_read_write(tmp_path, lines): + file_path = tmp_path / "test.csv" + write_csv_for_test(file_path, lines) + data = read_csv_test(file_path) + # Expecting list of dicts + assert data == [{"a": "1", "b": "2", "c": "3"}, + {"a": "4", "b": "5", "c": "6"}] + +def test_json_read_write(tmp_path): + file_path = tmp_path / "test.json" + data = {"name": "Alice", "age": 30} + write_json_test(file_path, data) + loaded = read_json_test(file_path) + assert loaded == data + +def test_yaml_read_write(tmp_path): + file_path = tmp_path / "test.yaml" + data = {"name": "Bob", "active": True, "scores": [1, 2, 3]} + write_yaml_test(file_path, data) + loaded = read_yaml_test(file_path) + assert loaded == data + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_write_config_test(tmp_path, ext): + file_path = tmp_path / f"config{ext}" + data = {"key": "value"} + write_config_test(file_path, data) + if ext == ".json": + loaded = read_json_test(file_path) + else: + loaded = read_yaml_test(file_path) + assert loaded == data + +def test_write_config_test_unsupported(tmp_path): + file_path = tmp_path / "config.txt" + with pytest.raises(ValueError): + write_config_test(file_path, {"key": "value"}) diff --git a/Tests/__init__.py b/Tests/__init__.py new file mode 100644 index 0000000..695c427 --- /dev/null +++ b/Tests/__init__.py @@ -0,0 +1,26 @@ +#Utility/ +#test_UnitConverter.py +#test_Logger.py +#test_DeltaTime.py +#test_Debug.py +#test_ConfigManager.py + +#Suporting/ +#test_yamlWriter.py +#test_yamlReader.py +#test_jsonWriter.py +#test_jsonReader.py +#test_csvWriterLogger.py +#test_csvWriter.py +#test_csvReader.py + + +#DataProsesing/ +#test_PID.py +#test_map.py +#test_filter.py +#test_DataRW.py +#test_DataPrettifyer.py +#test_DataHelperFunctions.py +#test_DataFormater.py +#test_DataConsolPrinter.py \ No newline at end of file diff --git a/Utility/ConfigManager.py b/Utility/ConfigManager.py index 133840a..bc7e5f5 100644 --- a/Utility/ConfigManager.py +++ b/Utility/ConfigManager.py @@ -2,7 +2,6 @@ import yaml import os import copy - from ..Utility.Debug import * from ..DataProsesing.DataRW import * @@ -81,30 +80,39 @@ def update(self, key, value): :param key: (str) Key to update (e.g., 'robot_legs.leg_1.joints[0].pid.kp'). :param value: New value to set. - :raises KeyError: If the key does not exist in the configuration. + :raises Exception: If the key path does not exist or types do not match. """ Debug.log(f"ConfigManager update","Header",group="LIB") + if self.config is None: + raise Exception("No configuration loaded to update.") + keys = key.split('.') config_section = self.config - for i, k in enumerate(keys[:-1]): - # Detect array indices in keys (e.g., 'active[0]') - if "[" in k and "]" in k: - array_key, index = k[:-1].split("[") - config_section = config_section[array_key][int(index)] - else: - k = int(k) if k.isdigit() else k # Convert to integer for list indices - config_section = config_section[k] + try: + # Traverse to the parent of the final key + for k in keys[:-1]: + if "[" in k and k.endswith("]"): + array_key, index = k[:-1].split("[") + array_key = int(array_key) if str(array_key).isdigit() else array_key + config_section = config_section[array_key][int(index)] + else: + k_idx = int(k) if str(k).isdigit() else k + config_section = config_section[k_idx] + - # Handle the final key final_key = keys[-1] - if "[" in final_key and "]" in final_key: + if "[" in final_key and final_key.endswith("]"): array_key, index = final_key[:-1].split("[") + array_key = int(array_key) if str(array_key).isdigit() else array_key config_section[array_key][int(index)] = value else: - final_key = int(final_key) if final_key.isdigit() else final_key - config_section[final_key] = value + final_key_idx = int(final_key) if str(final_key).isdigit() else final_key + config_section[final_key_idx] = value + + except (KeyError, IndexError, TypeError) as e: + raise Exception(f"Failed to update key '{key}': {e}") Debug.log(f"ConfigManager update","End",group="LIB") diff --git a/Utility/Logger.py b/Utility/Logger.py index a2bb33b..aaad00d 100644 --- a/Utility/Logger.py +++ b/Utility/Logger.py @@ -3,9 +3,10 @@ import csv class Logger: - file_path = None - file_encoding = None - first_write_done = True + # simplified names + path = None + encoding = None + first_write = True headers = ["message", "message_type", "group", "filename", "filepath", "lineno", "timestamp"] @classmethod @@ -17,50 +18,56 @@ def start_logger(cls, file_path: str = "", character_encoding: str = 'utf-8'): # Default folder if no path is provided if not file_path: file_path = "log/" - - # If only a directory is provided, generate unique filename + + # If only a directory is provided (no basename) generate unique filename if not os.path.basename(file_path): unique_id = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = os.path.join(file_path, f"log_{unique_id}.csv") - cls.file_path = os.path.abspath(file_path) - cls.file_encoding = character_encoding + cls.path = os.path.abspath(file_path) + cls.encoding = character_encoding # Ensure the directory exists - os.makedirs(os.path.dirname(cls.file_path), exist_ok=True) + os.makedirs(os.path.dirname(cls.path), exist_ok=True) # Initialize CSV with headers if file doesn't exist - if not os.path.exists(cls.file_path): - with open(cls.file_path, mode="w", encoding=cls.file_encoding, newline="") as f: + if not os.path.exists(cls.path): + with open(cls.path, mode="w", encoding=cls.encoding, newline="") as f: writer = csv.DictWriter(f, fieldnames=cls.headers) writer.writeheader() - cls.first_write_done = False + cls.first_write = False @classmethod def save_to_file(cls, data: list[dict]): from ..Suporting.csvWriterLogger import write_csv - first_write = not cls.first_write_done + first_write = not cls.first_write mode = "w" if first_write else "a" data_mode = "all" if first_write else "body" - write_csv(cls.file_path, data=data, mode=mode, data_mode=data_mode) - cls.first_write_done = True + write_csv(cls.path, data=data, mode=mode, data_mode=data_mode) + cls.first_write = True @classmethod def log(cls, message: str, message_type: str = "-", group: str = None, verbose=None): """Log one message entry""" - if cls.file_path is None: + if cls.path is None: raise RuntimeError("Logger not started. Call start_logger first.") + # support None/partial verbose objects + filename = getattr(verbose, "filename", "") if verbose is not None else "" + filepath = os.path.dirname(filename) if filename else "" + fname = os.path.basename(filename) if filename else "" + lineno = getattr(verbose, "lineno", 0) if verbose is not None else 0 + entry = { "message": message, "message_type": message_type, "group": group or "", - "filename": os.path.basename(verbose.filename), - "filepath": os.path.dirname(verbose.filename), - "lineno": verbose.lineno, + "filename": fname, + "filepath": filepath, + "lineno": lineno, "timestamp": datetime.now().isoformat() } diff --git a/Utility/UnitConverter.py b/Utility/UnitConverter.py index 9c2783e..348dff5 100644 --- a/Utility/UnitConverter.py +++ b/Utility/UnitConverter.py @@ -1,4 +1,3 @@ -import GabesPythonToolBox.Utility.ConfigManager as CM # will be used in future from ..Utility.Debug import Debug @@ -36,14 +35,14 @@ def convert(self, value, from_unit, to_unit, dimension): from_conf = dim_conf[from_unit] to_conf = dim_conf[to_unit] - # Determine if this is a temperature-style unit (has offset) + # Determine if this has offset if "offset" in from_conf or "offset" in to_conf: from_coef = from_conf.get("coefficient", 1) from_offset = from_conf.get("offset", 0) to_coef = to_conf.get("coefficient", 1) to_offset = to_conf.get("offset", 0) - # Convert to base (e.g., Kelvin) then to target + # Convert to base then to target value_in_base = (value - from_offset) / from_coef converted = value_in_base * to_coef + to_offset else: diff --git a/__init__.py b/__init__.py index 6086411..4567c9b 100644 --- a/__init__.py +++ b/__init__.py @@ -34,6 +34,11 @@ from .Utility.Logger import * from .Utility.UnitConverter import * +# ----------------------------- +# Utility +# ----------------------------- +from .Tests.UnitTestComon.UntTestUtility import* + # ----------------------------- # Public Exports # ----------------------------- @@ -63,4 +68,7 @@ "DeltaTime", "Logger", "UnitConverter", + + # UnitTest + "UntTestUtility" ] diff --git a/exsampleFiles/testPID.py b/exsampleFiles/testPID.py index dd45ffa..259431d 100644 --- a/exsampleFiles/testPID.py +++ b/exsampleFiles/testPID.py @@ -1,6 +1,6 @@ from GabesPythonToolBox.Utility.Debug import Debug Debug.add_group('Showcase', True) -Debug.add_group('LIB', True) +Debug.add_group('LIB', False) path = "exsampleFiles/temp/log/" Debug.set_log_enabled(path, enabled=False) @@ -18,7 +18,7 @@ def __init__(self, initial_value=0, gain=1, resistance=0.1, delay=0.1, min_contr self.current_value = initial_value self.gain = gain self.resistance = resistance # Resistance to change - self.delay = delay # Time lag effect + self.delay = delay # Time lag effect #TODO shood use dt self.previous_value = initial_value self.min_control_signal = min_control_signal self.max_control_signal = max_control_signal @@ -80,7 +80,7 @@ def test(): Debug.log(f"Simulation finished. ", "End", group="Showcase") graph(results) - val_graph(results) + #val_graph(results) diff --git a/folder_tree.txt b/folder_tree.txt new file mode 100644 index 0000000..fcdb2ab Binary files /dev/null and b/folder_tree.txt differ diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..d19922d Binary files /dev/null and b/requirements.in differ diff --git a/setup.py b/setup.py index 9048ad9..1d42c4c 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,16 @@ from setuptools import setup, find_packages setup( - name='GabrielsPythonToolBox', # Name of your package - version='1.8.2', # fundemental change.finished feature.bug fix - packages=find_packages(), # Automatically finds all packages - install_requires=[ # List your dependencies here + name='GabrielsPythonToolBox', + version='1.9.2', # "fundemental change"."finished feature"."bug fix" + packages=find_packages(), + install_requires=[ "matplotlib>=3.10.6,<3.11", "PyYAML>=6.0.2,<7.0", + "pytest>=8.4.2,<8.5", + "pytest-cov", + "pytest", + "setuptools" ], @@ -15,8 +19,8 @@ description="A collection of my useful Python tools", long_description=open('README.md').read(), long_description_content_type='text/markdown', - url="https://github.com/NorgeSkiFollo/GabrielsPythonToolBox.git", # Replace with your repo URL - classifiers=[ # Classifiers for your library + url="https://github.com/NorgeSkiFollo/GabrielsPythonToolBox.git", + classifiers=[ 'Programming Language :: Python :: 3', "License :: OSI Approved :: MIT License", 'Operating System :: OS Independent',