LiteBuild 0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
litebuild-0.2/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 corb555
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: LiteBuild
3
+ Version: 0.2
4
+ Summary: Lightweight build engine
5
+ Author: corb
6
+ License: Copyright 2025 corb555
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ Project-URL: Repository, https://github.com/corb555/LiteBuild
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Scientific/Engineering :: GIS
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Operating System :: MacOS
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: joblib>=1.5
26
+ Requires-Dist: matplotlib>=3.10
27
+ Requires-Dist: numpy>=2.2.6
28
+ Requires-Dist: pandas>=2.2.3
29
+ Requires-Dist: scikit-learn>=1.6.1
30
+ Requires-Dist: flashtext>=2.7
31
+ Requires-Dist: YMLEditor>=0.5.0
32
+ Requires-Dist: optuna>=4.5.0
33
+ Requires-Dist: shap>=0.47.2
34
+ Dynamic: license-file
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ pyproject.toml
3
+ LiteBuild.egg-info/PKG-INFO
4
+ LiteBuild.egg-info/SOURCES.txt
5
+ LiteBuild.egg-info/dependency_links.txt
6
+ LiteBuild.egg-info/entry_points.txt
7
+ LiteBuild.egg-info/requires.txt
8
+ LiteBuild.egg-info/top_level.txt
9
+ tests/test_build_engine.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ LiteBuild = LiteBuild.lite_build_runner:main
@@ -0,0 +1,9 @@
1
+ joblib>=1.5
2
+ matplotlib>=3.10
3
+ numpy>=2.2.6
4
+ pandas>=2.2.3
5
+ scikit-learn>=1.6.1
6
+ flashtext>=2.7
7
+ YMLEditor>=0.5.0
8
+ optuna>=4.5.0
9
+ shap>=0.47.2
@@ -0,0 +1,3 @@
1
+ build
2
+ dist
3
+ docs
litebuild-0.2/PKG-INFO ADDED
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: LiteBuild
3
+ Version: 0.2
4
+ Summary: Lightweight build engine
5
+ Author: corb
6
+ License: Copyright 2025 corb555
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ Project-URL: Repository, https://github.com/corb555/LiteBuild
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Scientific/Engineering :: GIS
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Operating System :: MacOS
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: joblib>=1.5
26
+ Requires-Dist: matplotlib>=3.10
27
+ Requires-Dist: numpy>=2.2.6
28
+ Requires-Dist: pandas>=2.2.3
29
+ Requires-Dist: scikit-learn>=1.6.1
30
+ Requires-Dist: flashtext>=2.7
31
+ Requires-Dist: YMLEditor>=0.5.0
32
+ Requires-Dist: optuna>=4.5.0
33
+ Requires-Dist: shap>=0.47.2
34
+ Dynamic: license-file
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "LiteBuild"
7
+ version = "0.2"
8
+ description = "Lightweight build engine"
9
+ authors = [{ name = "corb" }]
10
+ readme = { file = "readme.md", content-type = "text/markdown" }
11
+ license = { file = "LICENSE" }
12
+ requires-python = ">=3.9"
13
+ dependencies = [
14
+ "joblib>=1.5",
15
+ "matplotlib>=3.10",
16
+ "numpy>=2.2.6",
17
+ "pandas>=2.2.3",
18
+ "scikit-learn>=1.6.1",
19
+ "flashtext>=2.7",
20
+ "YMLEditor>=0.5.0",
21
+ "optuna>=4.5.0",
22
+ "shap>=0.47.2",
23
+ ]
24
+
25
+ classifiers = [
26
+ "Intended Audience :: Developers",
27
+ "Topic :: Scientific/Engineering :: GIS",
28
+ "Programming Language :: Python :: 3.8",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Operating System :: POSIX :: Linux",
33
+ "Operating System :: MacOS",
34
+ ]
35
+
36
+ [project.scripts]
37
+ LiteBuild = "LiteBuild.lite_build_runner:main"
38
+
39
+ [project.urls]
40
+ Repository = "https://github.com/corb555/LiteBuild"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["."]
44
+ exclude = ["tests*"]
45
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,248 @@
1
+ # tests/test_build_engine.py
2
+
3
+ import time
4
+
5
+ import pytest
6
+
7
+ # Assume build_engine and its dependencies are in the python path
8
+ from build_engine import BuildPlanner, UpdateCode
9
+
10
+
11
+ # --- Test Fixtures and Test Data ---
12
+
13
+ @pytest.fixture
14
+ def base_config():
15
+ """Provides a basic, valid configuration for tests."""
16
+ return {
17
+ "GENERAL": {
18
+ "VAR1": "general_value"
19
+ }, "PROFILES": {
20
+ "test_profile": {
21
+ "VAR2": "profile_value"
22
+ }
23
+ }, "WORKFLOW": {
24
+ "StepA": {
25
+ "OUTPUT": "output_a.txt", "REQUIRES": [],
26
+ "RULE": {"NAME": "rule_a", "COMMAND": "echo A > {OUTPUT}"}
27
+ }, "StepB": {
28
+ "OUTPUT": "output_b.txt", "REQUIRES": ["StepA"], "INPUTS": ["{REQUIRES[0]}"],
29
+ "RULE": {"NAME": "rule_b", "COMMAND": "cat {INPUTS[0]} > {OUTPUT}"}
30
+ }
31
+ }
32
+ }
33
+
34
+
35
+ @pytest.fixture
36
+ def command_map():
37
+ """Provides a sample command map, simulating the output of CommandGenerator."""
38
+ return {
39
+ "StepA": {
40
+ "output": "output_a.txt", "input_files": [], "hashes": {
41
+ "command": "hash_cmd_a", "inputs": "hash_inputs_a", "params": "hash_params_a"
42
+ }
43
+ }, "StepB": {
44
+ "output": "output_b.txt", "input_files": ["output_a.txt"], "hashes": {
45
+ "command": "hash_cmd_b", "inputs": "hash_inputs_b", "params": "hash_params_b"
46
+ }
47
+ }
48
+ }
49
+
50
+
51
+ @pytest.fixture
52
+ def execution_graph(base_config):
53
+ """Provides a sample NetworkX graph."""
54
+ # In a real scenario, this would be generated by DependencyGraph
55
+ import networkx as nx
56
+ G = nx.DiGraph()
57
+ G.add_node("StepA", **base_config["WORKFLOW"]["StepA"])
58
+ G.add_node("StepB", **base_config["WORKFLOW"]["StepB"])
59
+ G.add_edge("StepA", "StepB")
60
+ return G
61
+
62
+
63
+ # --- Parameterized Tests for `_is_step_outdated` ---
64
+
65
+ # Define the test cases for various outdated scenarios
66
+ # Each tuple is: (test_id, build_state, expected_code, expected_context)
67
+ outdated_test_cases = [pytest.param(
68
+ "up_to_date", {
69
+ "output_a.txt": {
70
+ "mtime": time.time() - 10, "hashes": {
71
+ "command": "hash_cmd_a", "inputs": "hash_inputs_a", "params": "hash_params_a"
72
+ }
73
+ }
74
+ }, UpdateCode.UP_TO_DATE, "", id="up_to_date"
75
+ ), pytest.param(
76
+ "missing_output", {}, # Build state is empty, so output_a.txt is not tracked
77
+ UpdateCode.MISSING_OUTPUT, "output_a.txt", id="missing_output"
78
+ ), pytest.param(
79
+ "not_tracked", {"some_other_file.txt": {}}, # output_a.txt is not a key
80
+ UpdateCode.NOT_TRACKED, "output_a.txt", id="not_tracked"
81
+ ), pytest.param(
82
+ "command_changed", {
83
+ "output_a.txt": {
84
+ "mtime": time.time() - 10, "hashes": {
85
+ "command": "hash_cmd_a_DIFFERENT", "inputs": "hash_inputs_a",
86
+ "params": "hash_params_a"
87
+ }
88
+ }
89
+ }, UpdateCode.COMMAND_CHANGED, "", id="command_changed"
90
+ ), pytest.param(
91
+ "inputs_changed", {
92
+ "output_a.txt": {
93
+ "mtime": time.time() - 10, "hashes": {
94
+ "command": "hash_cmd_a", "inputs": "hash_inputs_a_DIFFERENT",
95
+ "params": "hash_params_a"
96
+ }
97
+ }
98
+ }, UpdateCode.INPUTS_CHANGED, "", id="inputs_changed"
99
+ ), pytest.param(
100
+ "params_changed", {
101
+ "output_a.txt": {
102
+ "mtime": time.time() - 10, "hashes": {
103
+ "command": "hash_cmd_a", "inputs": "hash_inputs_a",
104
+ "params": "hash_params_a_DIFFERENT"
105
+ }
106
+ }
107
+ }, UpdateCode.PARAMS_CHANGED, "", id="params_changed"
108
+ ), ]
109
+
110
+
111
+ @pytest.mark.parametrize(
112
+ "test_id, build_state, expected_code, expected_context", outdated_test_cases
113
+ )
114
+ def test_is_step_outdated_scenarios(
115
+ mocker, base_config, command_map, test_id, build_state, expected_code, expected_context
116
+ ):
117
+ """
118
+ Tests the _is_step_outdated method with various states using parameterization.
119
+ We only test StepA here, as it has no file dependencies.
120
+ """
121
+ # Mock os.path.exists to simulate file presence or absence
122
+ if expected_code == UpdateCode.MISSING_OUTPUT:
123
+ mocker.patch("os.path.exists", return_value=False)
124
+ else:
125
+ mocker.patch("os.path.exists", return_value=True)
126
+
127
+ # Mock os.path.getmtime to avoid real file system access
128
+ mocker.patch("os.path.getmtime", return_value=time.time() - 20)
129
+
130
+ planner = BuildPlanner(base_config, build_state)
131
+ command_a = command_map["StepA"]
132
+
133
+ update_code, context = planner._is_step_outdated(command_a)
134
+
135
+ assert update_code == expected_code
136
+ assert context == expected_context
137
+
138
+
139
+ def test_is_step_outdated_newer_input(mocker, base_config, command_map):
140
+ """
141
+ Tests the specific case where an input file is newer than the last build.
142
+ We test StepB here, as it depends on StepA.
143
+ """
144
+ # Simulate that both files exist
145
+ mocker.patch("os.path.exists", return_value=True)
146
+
147
+ # Simulate that the input (output_a.txt) is NEWER than the last build time
148
+ def mock_getmtime(path):
149
+ if "output_a.txt" in path:
150
+ return time.time() # Now
151
+ return time.time() - 20 # 20 seconds ago
152
+
153
+ mocker.patch("os.path.getmtime", side_effect=mock_getmtime)
154
+
155
+ build_state = {
156
+ "output_b.txt": {
157
+ "mtime": time.time() - 10, # Last build was 10 seconds ago
158
+ "hashes": {
159
+ "command": "hash_cmd_b", "inputs": "hash_inputs_b", "params": "hash_params_b"
160
+ }
161
+ }
162
+ }
163
+
164
+ planner = BuildPlanner(base_config, build_state)
165
+ command_b = command_map["StepB"]
166
+
167
+ update_code, context = planner._is_step_outdated(command_b)
168
+
169
+ assert update_code == UpdateCode.NEWER_INPUT
170
+ assert context == "output_a.txt"
171
+
172
+
173
+ # --- Tests for the overall `plan_build` method ---
174
+
175
+ def test_plan_build_full_rebuild(mocker, base_config, command_map, execution_graph):
176
+ """
177
+ Tests that a full rebuild is planned when the build state is empty.
178
+ """
179
+ # Mock the command generation and graph creation
180
+ mocker.patch.object(
181
+ BuildPlanner, "_generate_command_map_and_graph", return_value=(command_map, execution_graph)
182
+ )
183
+
184
+ # Mock the outdated check to always return MISSING_OUTPUT for simplicity
185
+ mocker.patch.object(
186
+ BuildPlanner, "_is_step_outdated",
187
+ return_value=(UpdateCode.MISSING_OUTPUT, "mock_output.txt")
188
+ )
189
+
190
+ planner = BuildPlanner(base_config, build_state={})
191
+ plan = planner.plan_build(profile_name="test_profile")
192
+
193
+ assert len(plan.steps_to_run) == 2
194
+ assert len(plan.steps_to_skip) == 0
195
+ assert plan.steps_to_run[0].node_name == "StepA"
196
+ assert plan.steps_to_run[1].node_name == "StepB"
197
+
198
+
199
+ def test_plan_build_partial_rebuild(mocker, base_config, command_map, execution_graph):
200
+ """
201
+ Tests that a partial rebuild is planned when only one step is outdated,
202
+ and the change is correctly propagated to its descendants.
203
+ """
204
+ mocker.patch.object(
205
+ BuildPlanner, "_generate_command_map_and_graph", return_value=(command_map, execution_graph)
206
+ )
207
+
208
+ # Mock the outdated check: StepA is outdated, StepB is not (yet).
209
+ def mock_is_outdated(command):
210
+ if command['output'] == "output_a.txt":
211
+ return (UpdateCode.COMMAND_CHANGED, "")
212
+ return (UpdateCode.UP_TO_DATE, "")
213
+
214
+ mocker.patch.object(BuildPlanner, "_is_step_outdated", side_effect=mock_is_outdated)
215
+
216
+ planner = BuildPlanner(base_config, build_state={"mock": "state"})
217
+ plan = planner.plan_build(profile_name="test_profile")
218
+
219
+ # StepA is outdated, and StepB must be rebuilt because its dependency changed.
220
+ assert len(plan.steps_to_run) == 2
221
+ assert len(plan.steps_to_skip) == 0
222
+
223
+ # Check the reasons for the rebuild
224
+ step_a_plan = next(s for s in plan.steps_to_run if s.node_name == "StepA")
225
+ step_b_plan = next(s for s in plan.steps_to_run if s.node_name == "StepB")
226
+
227
+ assert step_a_plan.update_code == UpdateCode.COMMAND_CHANGED
228
+ assert step_b_plan.update_code == UpdateCode.DEPENDENCY_CHANGED
229
+
230
+
231
+ def test_plan_build_no_rebuild(mocker, base_config, command_map, execution_graph):
232
+ """
233
+ Tests that no steps are planned to run when everything is up-to-date.
234
+ """
235
+ mocker.patch.object(
236
+ BuildPlanner, "_generate_command_map_and_graph", return_value=(command_map, execution_graph)
237
+ )
238
+
239
+ # Mock the outdated check to always return UP_TO_DATE
240
+ mocker.patch.object(
241
+ BuildPlanner, "_is_step_outdated", return_value=(UpdateCode.UP_TO_DATE, "")
242
+ )
243
+
244
+ planner = BuildPlanner(base_config, build_state={"mock": "state"})
245
+ plan = planner.plan_build(profile_name="test_profile")
246
+
247
+ assert len(plan.steps_to_run) == 0
248
+ assert len(plan.steps_to_skip) == 2