sheaffoundry 0.1.0a1__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.
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: sheaffoundry
3
+ Version: 0.1.0a1
4
+ Summary: Deterministic assembly plans for named inputs and reproducible build receipts.
5
+ Project-URL: Repository, https://github.com/SheafLab/sheaffoundry-python
6
+ Author: SheafLab
7
+ License: MIT
8
+ Keywords: assembly,plans,provenance,reproducibility,sheaflab
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+
20
+ # sheaffoundry
21
+
22
+ `sheaffoundry` models a small, reviewable assembly plan. A plan names its
23
+ inputs, orders assembly steps, and emits a deterministic receipt suitable for
24
+ logs, test fixtures, or provenance records.
25
+
26
+ ```python
27
+ from sheaffoundry import AssemblyPlan, Input, Step
28
+
29
+ plan = AssemblyPlan(
30
+ "preview-image",
31
+ inputs=(Input("source", "sha256:abc"), Input("palette", "v3")),
32
+ steps=(Step("decode"), Step("render", needs=("decode",))),
33
+ )
34
+
35
+ receipt = plan.receipt()
36
+ assert receipt.step_order == ("decode", "render")
37
+ ```
38
+
39
+ Steps form a directed acyclic graph. The package validates missing dependencies
40
+ and cycles, then uses lexical tie-breaking to produce a stable execution order.
41
+ There are no runtime dependencies.
@@ -0,0 +1,22 @@
1
+ # sheaffoundry
2
+
3
+ `sheaffoundry` models a small, reviewable assembly plan. A plan names its
4
+ inputs, orders assembly steps, and emits a deterministic receipt suitable for
5
+ logs, test fixtures, or provenance records.
6
+
7
+ ```python
8
+ from sheaffoundry import AssemblyPlan, Input, Step
9
+
10
+ plan = AssemblyPlan(
11
+ "preview-image",
12
+ inputs=(Input("source", "sha256:abc"), Input("palette", "v3")),
13
+ steps=(Step("decode"), Step("render", needs=("decode",))),
14
+ )
15
+
16
+ receipt = plan.receipt()
17
+ assert receipt.step_order == ("decode", "render")
18
+ ```
19
+
20
+ Steps form a directed acyclic graph. The package validates missing dependencies
21
+ and cycles, then uses lexical tie-breaking to produce a stable execution order.
22
+ There are no runtime dependencies.
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sheaffoundry"
7
+ version = "0.1.0a1"
8
+ description = "Deterministic assembly plans for named inputs and reproducible build receipts."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "SheafLab" }]
13
+ keywords = ["assembly", "provenance", "plans", "reproducibility", "sheaflab"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+
25
+ [project.urls]
26
+ Repository = "https://github.com/SheafLab/sheaffoundry-python"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/sheaffoundry"]
@@ -0,0 +1,4 @@
1
+ from .plan import AssemblyPlan, Input, Receipt, Step
2
+
3
+ __all__ = ["AssemblyPlan", "Input", "Receipt", "Step"]
4
+ __version__ = "0.1.0a1"
@@ -0,0 +1,74 @@
1
+ """Dependency-checked, deterministic assembly plans."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class Input:
10
+ name: str
11
+ reference: str
12
+
13
+ def __post_init__(self) -> None:
14
+ if not self.name.strip() or not self.reference.strip():
15
+ raise ValueError("inputs require non-empty names and references")
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class Step:
20
+ name: str
21
+ needs: tuple[str, ...] = ()
22
+
23
+ def __post_init__(self) -> None:
24
+ if not self.name.strip():
25
+ raise ValueError("steps require a non-empty name")
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class Receipt:
30
+ plan: str
31
+ inputs: tuple[Input, ...]
32
+ step_order: tuple[str, ...]
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class AssemblyPlan:
37
+ name: str
38
+ inputs: tuple[Input, ...] = ()
39
+ steps: tuple[Step, ...] = ()
40
+
41
+ def __post_init__(self) -> None:
42
+ if not self.name.strip():
43
+ raise ValueError("plans require a non-empty name")
44
+ if len({item.name for item in self.inputs}) != len(self.inputs):
45
+ raise ValueError("input names must be unique")
46
+ if len({step.name for step in self.steps}) != len(self.steps):
47
+ raise ValueError("step names must be unique")
48
+ known = {step.name for step in self.steps}
49
+ missing = sorted({dependency for step in self.steps for dependency in step.needs} - known)
50
+ if missing:
51
+ raise ValueError(f"unknown step dependencies: {', '.join(missing)}")
52
+ self.execution_order()
53
+
54
+ def execution_order(self) -> tuple[str, ...]:
55
+ """Topologically order steps with deterministic tie-breaking."""
56
+
57
+ waiting = {step.name: set(step.needs) for step in self.steps}
58
+ ordered: list[str] = []
59
+ while waiting:
60
+ ready = sorted(name for name, needs in waiting.items() if not needs)
61
+ if not ready:
62
+ raise ValueError("step dependencies contain a cycle")
63
+ for name in ready:
64
+ del waiting[name]
65
+ ordered.append(name)
66
+ completed = set(ready)
67
+ for needs in waiting.values():
68
+ needs.difference_update(completed)
69
+ return tuple(ordered)
70
+
71
+ def receipt(self) -> Receipt:
72
+ """Capture the plan's stable inputs and execution order."""
73
+
74
+ return Receipt(self.name, tuple(sorted(self.inputs, key=lambda item: item.name)), self.execution_order())
@@ -0,0 +1,17 @@
1
+ import unittest
2
+
3
+ from sheaffoundry import AssemblyPlan, Input, Step
4
+
5
+
6
+ class AssemblyPlanTests(unittest.TestCase):
7
+ def test_execution_order_is_stable(self) -> None:
8
+ plan = AssemblyPlan("demo", steps=(Step("publish", ("build",)), Step("build"), Step("audit")))
9
+ self.assertEqual(plan.execution_order(), ("audit", "build", "publish"))
10
+
11
+ def test_receipt_sorts_inputs(self) -> None:
12
+ plan = AssemblyPlan("demo", inputs=(Input("z", "3"), Input("a", "1")))
13
+ self.assertEqual(tuple(item.name for item in plan.receipt().inputs), ("a", "z"))
14
+
15
+ def test_cycle_is_rejected(self) -> None:
16
+ with self.assertRaises(ValueError):
17
+ AssemblyPlan("cycle", steps=(Step("a", ("b",)), Step("b", ("a",))))