furu 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl
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.
- furu/config.py +27 -40
- furu/core/furu.py +194 -126
- furu/core/list.py +3 -2
- furu/dashboard/frontend/dist/assets/{index-DS3FsqcY.js → index-BjyrY-Zz.js} +1 -1
- furu/dashboard/frontend/dist/index.html +1 -1
- furu/execution/local.py +9 -7
- furu/execution/plan.py +117 -25
- furu/execution/slurm_dag.py +16 -14
- furu/execution/slurm_pool.py +5 -5
- furu/execution/slurm_spec.py +2 -2
- furu/migration.py +1 -2
- furu/runtime/env.py +1 -1
- furu/runtime/logging.py +30 -4
- furu/storage/metadata.py +25 -29
- furu/storage/migration.py +0 -1
- furu/storage/state.py +86 -92
- {furu-0.0.4.dist-info → furu-0.0.5.dist-info}/METADATA +18 -6
- {furu-0.0.4.dist-info → furu-0.0.5.dist-info}/RECORD +20 -20
- {furu-0.0.4.dist-info → furu-0.0.5.dist-info}/WHEEL +1 -1
- {furu-0.0.4.dist-info → furu-0.0.5.dist-info}/entry_points.txt +0 -0
furu/config.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from importlib import import_module
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import Literal, cast
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
RecordGitMode = Literal["ignore", "cached", "uncached"]
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
class FuruConfig:
|
|
@@ -41,21 +45,14 @@ class FuruConfig:
|
|
|
41
45
|
"true",
|
|
42
46
|
"yes",
|
|
43
47
|
}
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"yes",
|
|
53
|
-
}
|
|
54
|
-
self.require_git_remote = os.getenv("FURU_REQUIRE_GIT_REMOTE", "1").lower() in {
|
|
55
|
-
"1",
|
|
56
|
-
"true",
|
|
57
|
-
"yes",
|
|
58
|
-
}
|
|
48
|
+
self.record_git = self._parse_record_git(os.getenv("FURU_RECORD_GIT", "cached"))
|
|
49
|
+
self.allow_no_git_origin = self._parse_bool(
|
|
50
|
+
os.getenv("FURU_ALLOW_NO_GIT_ORIGIN", "0")
|
|
51
|
+
)
|
|
52
|
+
if self.allow_no_git_origin and self.record_git == "ignore":
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"FURU_ALLOW_NO_GIT_ORIGIN cannot be enabled when FURU_RECORD_GIT=ignore"
|
|
55
|
+
)
|
|
59
56
|
always_rerun_items = {
|
|
60
57
|
item.strip()
|
|
61
58
|
for item in os.getenv("FURU_ALWAYS_RERUN", "").split(",")
|
|
@@ -77,35 +74,25 @@ class FuruConfig:
|
|
|
77
74
|
"FURU_CANCELLED_IS_PREEMPTED", "false"
|
|
78
75
|
).lower() in {"1", "true", "yes"}
|
|
79
76
|
|
|
80
|
-
# Parse FURU_CACHE_METADATA: "never", "forever", or duration like "5m", "1h"
|
|
81
|
-
# Default: "5m" (5 minutes) - balances performance with freshness
|
|
82
|
-
self.cache_metadata_ttl_sec: float | None = self._parse_cache_duration(
|
|
83
|
-
os.getenv("FURU_CACHE_METADATA", "5m")
|
|
84
|
-
)
|
|
85
|
-
|
|
86
77
|
@staticmethod
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Parse duration like "5m", "1h", "30s"
|
|
96
|
-
import re
|
|
97
|
-
|
|
98
|
-
match = re.match(r"^(\d+(?:\.\d+)?)\s*([smh]?)$", value)
|
|
99
|
-
if not match:
|
|
78
|
+
def _parse_bool(value: str) -> bool:
|
|
79
|
+
return value.strip().lower() in {"1", "true", "yes"}
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def _parse_record_git(cls, value: str) -> RecordGitMode:
|
|
83
|
+
normalized = value.strip().lower()
|
|
84
|
+
allowed = {"ignore", "cached", "uncached"}
|
|
85
|
+
if normalized not in allowed:
|
|
100
86
|
raise ValueError(
|
|
101
|
-
|
|
102
|
-
"Use 'never', 'forever', or duration like '5m', '1h', '30s'"
|
|
87
|
+
"FURU_RECORD_GIT must be one of 'ignore', 'cached', or 'uncached'"
|
|
103
88
|
)
|
|
89
|
+
return cast(RecordGitMode, normalized)
|
|
104
90
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
@property
|
|
92
|
+
def cache_metadata_ttl_sec(self) -> float | None:
|
|
93
|
+
if self.record_git == "cached":
|
|
94
|
+
return float("inf")
|
|
95
|
+
return None
|
|
109
96
|
|
|
110
97
|
def get_root(self, version_controlled: bool = False) -> Path:
|
|
111
98
|
"""Get root directory for storage (version_controlled uses its own root)."""
|