furu 0.0.4__py3-none-any.whl → 0.0.6__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 +203 -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/runtime/overrides.py +37 -0
- furu/storage/metadata.py +26 -29
- furu/storage/migration.py +0 -1
- furu/storage/state.py +86 -92
- furu/testing.py +232 -0
- {furu-0.0.4.dist-info → furu-0.0.6.dist-info}/METADATA +101 -6
- {furu-0.0.4.dist-info → furu-0.0.6.dist-info}/RECORD +22 -20
- {furu-0.0.4.dist-info → furu-0.0.6.dist-info}/WHEEL +1 -1
- {furu-0.0.4.dist-info → furu-0.0.6.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: furu
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Cacheable, nested pipelines for Python. Define computations as configs; furu handles caching, state tracking, and result reuse across runs.
|
|
5
5
|
Author: Herman Brunborg
|
|
6
6
|
Author-email: Herman Brunborg <herman@brunborg.com>
|
|
@@ -459,8 +459,11 @@ The `/api/experiments` endpoint supports:
|
|
|
459
459
|
|----------|---------|-------------|
|
|
460
460
|
| `FURU_PATH` | `<project>/furu-data` | Base storage directory for non-versioned artifacts |
|
|
461
461
|
| `FURU_VERSION_CONTROLLED_PATH` | `<project>/furu-data/artifacts` | Override version-controlled storage root |
|
|
462
|
+
| `FURU_SUBMITIT_PATH` | `<FURU_PATH>/submitit` | Override submitit logs root |
|
|
462
463
|
| `FURU_LOG_LEVEL` | `INFO` | Console verbosity (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
|
|
463
|
-
| `
|
|
464
|
+
| `FURU_RICH_UNCAUGHT_TRACEBACKS` | `true` | Use Rich for exception formatting (set `0` to disable) |
|
|
465
|
+
| `FURU_RECORD_GIT` | `cached` | Git provenance capture: `ignore` skips git metadata, `cached` records once per process, `uncached` records every time |
|
|
466
|
+
| `FURU_ALLOW_NO_GIT_ORIGIN` | `false` | Allow missing git `origin` when recording git metadata (invalid with `FURU_RECORD_GIT=ignore`) |
|
|
464
467
|
| `FURU_ALWAYS_RERUN` | `""` | Comma-separated class qualnames to always rerun (use `ALL` to bypass cache globally; cannot combine with other entries; entries must be importable) |
|
|
465
468
|
| `FURU_RETRY_FAILED` | `true` | Retry failed artifacts by default (set to `0` to keep failures sticky) |
|
|
466
469
|
| `FURU_MAX_COMPUTE_RETRIES` | `3` | Maximum compute retries per node after the first failure |
|
|
@@ -469,12 +472,21 @@ The `/api/experiments` endpoint supports:
|
|
|
469
472
|
| `FURU_WAIT_LOG_EVERY_SECS` | `10` | Interval between "waiting" log messages |
|
|
470
473
|
| `FURU_STALE_AFTER_SECS` | `1800` | Consider running jobs stale after this duration |
|
|
471
474
|
| `FURU_LEASE_SECS` | `120` | Compute lock lease duration |
|
|
472
|
-
| `FURU_HEARTBEAT_SECS` | `lease/3` | Heartbeat interval for running jobs |
|
|
475
|
+
| `FURU_HEARTBEAT_SECS` | `lease/3` | Heartbeat interval for running jobs (min 1s) |
|
|
473
476
|
| `FURU_PREEMPT_MAX` | `5` | Maximum submitit requeues on preemption |
|
|
474
477
|
| `FURU_CANCELLED_IS_PREEMPTED` | `false` | Treat SLURM CANCELLED as preempted |
|
|
475
|
-
| `
|
|
478
|
+
| `SLURM_JOB_ID` | unset | Read-only; set by Slurm to record job id and enable submitit context |
|
|
476
479
|
|
|
477
|
-
Local `.env` files are loaded automatically
|
|
480
|
+
Local `.env` files are not loaded automatically. Call `furu.load_env()` when you
|
|
481
|
+
want to load `.env` values (requires `python-dotenv`).
|
|
482
|
+
|
|
483
|
+
### Test and CI Environment Variables
|
|
484
|
+
|
|
485
|
+
| Variable | Default | Description |
|
|
486
|
+
|----------|---------|-------------|
|
|
487
|
+
| `FURU_DASHBOARD_DEV_DATA_DIR` | unset | Override data dir for `make dashboard-dev` (defaults to a temp dir) |
|
|
488
|
+
| `FURU_E2E_DATA_DIR` | unset | Required for Playwright e2e runs; used as the data root and to set `FURU_PATH` |
|
|
489
|
+
| `CI` | unset | Enables CI-friendly Playwright settings (retries, single worker, traces, screenshots, video) |
|
|
478
490
|
|
|
479
491
|
### Programmatic Configuration
|
|
480
492
|
|
|
@@ -487,10 +499,93 @@ furu.set_furu_root(Path("/my/storage"))
|
|
|
487
499
|
root = furu.get_furu_root()
|
|
488
500
|
|
|
489
501
|
# Access config directly
|
|
490
|
-
furu.FURU_CONFIG.
|
|
502
|
+
furu.FURU_CONFIG.record_git = "uncached"
|
|
491
503
|
furu.FURU_CONFIG.poll_interval = 5.0
|
|
492
504
|
```
|
|
493
505
|
|
|
506
|
+
### Testing with pytest
|
|
507
|
+
|
|
508
|
+
Use the built-in pytest fixture to isolate Furu storage in tests (each test gets
|
|
509
|
+
its own temp root, so identical configs in separate tests will not collide):
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
# conftest.py
|
|
513
|
+
pytest_plugins = ["furu.testing"]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
# test_pipeline.py
|
|
518
|
+
import json
|
|
519
|
+
from pathlib import Path
|
|
520
|
+
|
|
521
|
+
import furu
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class TrainModel(furu.Furu[Path]):
|
|
525
|
+
lr: float = furu.chz.field(default=1e-3)
|
|
526
|
+
|
|
527
|
+
def _create(self) -> Path:
|
|
528
|
+
path = self.furu_dir / "metrics.json"
|
|
529
|
+
path.write_text(json.dumps({"lr": self.lr}))
|
|
530
|
+
return path
|
|
531
|
+
|
|
532
|
+
def _load(self) -> Path:
|
|
533
|
+
return self.furu_dir / "metrics.json"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_create_and_reload(furu_tmp_root):
|
|
537
|
+
obj = TrainModel(lr=1e-3)
|
|
538
|
+
first = obj.get()
|
|
539
|
+
second = obj.get()
|
|
540
|
+
assert first.read_text() == second.read_text()
|
|
541
|
+
assert (furu_tmp_root / "data").exists()
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Override specific dependencies when you want to skip deeper chains:
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
from furu.testing import override_results
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class Normalize(furu.Furu[str]):
|
|
551
|
+
def _create(self) -> str:
|
|
552
|
+
return "normalized"
|
|
553
|
+
|
|
554
|
+
def _load(self) -> str:
|
|
555
|
+
return "normalized"
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
class TrainModel(furu.Furu[str]):
|
|
559
|
+
normalizer: Normalize = furu.chz.field(default_factory=Normalize)
|
|
560
|
+
|
|
561
|
+
def _create(self) -> str:
|
|
562
|
+
return f"trained:{self.normalizer.get()}"
|
|
563
|
+
|
|
564
|
+
def _load(self) -> str:
|
|
565
|
+
return "trained"
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def test_override_dependency(furu_tmp_root):
|
|
569
|
+
normalizer = Normalize()
|
|
570
|
+
model = TrainModel(normalizer=normalizer)
|
|
571
|
+
with override_results({normalizer: "stub"}):
|
|
572
|
+
assert model.get() == "trained:stub"
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
If you want to override without instantiating the dependency directly, target it
|
|
576
|
+
by dotted path from the root object (chz-style paths, e.g. `deps.0` for lists and
|
|
577
|
+
`deps.key` for mappings):
|
|
578
|
+
|
|
579
|
+
```python
|
|
580
|
+
from furu.testing import override_results_for
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def test_override_by_path(furu_tmp_root):
|
|
584
|
+
model = TrainModel()
|
|
585
|
+
with override_results_for(model, {"normalizer": "stub"}):
|
|
586
|
+
assert model.get() == "trained:stub"
|
|
587
|
+
```
|
|
588
|
+
|
|
494
589
|
### Class-Level Options
|
|
495
590
|
|
|
496
591
|
```python
|
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
furu/__init__.py,sha256=Z8VssTuQm2nH7bgB8SQc8pXsNGc-H1QGHFffKzNzqk8,2018
|
|
2
2
|
furu/adapters/__init__.py,sha256=onLzEj9hccPK15g8a8va2T19nqQXoxb9rQlJIjKSKnE,69
|
|
3
3
|
furu/adapters/submitit.py,sha256=FV3XEUSQuS5vIyzkW-Iuqtf8SRL-fsokPG67u7tMF5I,7276
|
|
4
|
-
furu/config.py,sha256=
|
|
4
|
+
furu/config.py,sha256=UGnH8QAKMUgrGMGNkfBgLXideXEpDlozUSsX9iNN8Lw,6844
|
|
5
5
|
furu/core/__init__.py,sha256=6hH7i6r627c0FZn6eQVsSG7LD4QmTta6iQw0AiPQPTM,156
|
|
6
|
-
furu/core/furu.py,sha256=
|
|
7
|
-
furu/core/list.py,sha256=
|
|
6
|
+
furu/core/furu.py,sha256=2KTeMjQjRoDaXNIoKUlYmJ0JWJFG4krom7w6UsUpvDg,61785
|
|
7
|
+
furu/core/list.py,sha256=QaGSh8NFg1K2WFncM8duOYQ6KLZ6EW2pRLArN_e5Juw,3662
|
|
8
8
|
furu/dashboard/__init__.py,sha256=ziAordJfkbbXNIM7iA9O7vR2gsCq34AInYiMYOCfWOc,362
|
|
9
9
|
furu/dashboard/__main__.py,sha256=cNs65IMl4kwZFpxa9xLXmFSy4-M5D1X1ZBfTDxW11vo,144
|
|
10
10
|
furu/dashboard/api/__init__.py,sha256=9-WyWOt-VQJJBIsdW29D-7JvR-BivJd9G_SRaRptCz0,80
|
|
11
11
|
furu/dashboard/api/models.py,sha256=SCu-kLJyW7dwSKswdgQNS3wQuj25ORs0pHkvX9xBbo4,4767
|
|
12
12
|
furu/dashboard/api/routes.py,sha256=iZez0khIUvbgfeSoy1BJvmoEEbgUrdSQA8SN8iAIkM8,4813
|
|
13
13
|
furu/dashboard/frontend/dist/assets/index-BXAIKNNr.css,sha256=qhsN0Td3mM-GAR8mZ0CtocynABLKa1ncl9ioDrTKOIQ,34768
|
|
14
|
-
furu/dashboard/frontend/dist/assets/index-
|
|
14
|
+
furu/dashboard/frontend/dist/assets/index-BjyrY-Zz.js,sha256=fItsQ--Dzobq5KdUcuqDi4txM2-NNqx8JET5Lwkwf7U,544515
|
|
15
15
|
furu/dashboard/frontend/dist/favicon.svg,sha256=3TSLHNZITFe3JTPoYHZnDgiGsJxIzf39v97l2A1Hodo,369
|
|
16
|
-
furu/dashboard/frontend/dist/index.html,sha256=
|
|
16
|
+
furu/dashboard/frontend/dist/index.html,sha256=Ig-j0qgTXBSge0GN7PaM7mcLnuRhRMQmkTZjU1wmTXY,810
|
|
17
17
|
furu/dashboard/main.py,sha256=gj9Cdj2qyaSCEkmfNHUMQXlXv6GpWTQ9IZEi7WzlCSo,4463
|
|
18
18
|
furu/dashboard/scanner.py,sha256=qXCvkvFByBc09TUdth5Js67rS8zpRBlRkVQ9dJ7YbdE,34696
|
|
19
19
|
furu/errors.py,sha256=FFbV4M0-ipVGizv5ee80L-NZFVjaRjy8i19mClr6R0g,3959
|
|
20
20
|
furu/execution/__init__.py,sha256=ixVw1Shvg2ulS597OYYeGgSSTwv25j_McuQdDXIiEL8,625
|
|
21
21
|
furu/execution/context.py,sha256=0tAbM0azqEus8hknf_A9-Zs9Sq99bnUkFyV4RO4ZMRU,666
|
|
22
|
-
furu/execution/local.py,sha256=
|
|
22
|
+
furu/execution/local.py,sha256=SXUH9PfcCAeHSZYrTP1YNjl2fV7vqzpZXZQzmtIcVMg,7137
|
|
23
23
|
furu/execution/paths.py,sha256=0MfQk5Kh7bxvJiWvG40TJe7RF5Q5Na6uvi6qV0OT3Vc,460
|
|
24
|
-
furu/execution/plan.py,sha256=
|
|
24
|
+
furu/execution/plan.py,sha256=bEnzFlBVN3vGKb_0a03sOEtJYRkLSJkIaNzGnxjZTo4,9928
|
|
25
25
|
furu/execution/plan_utils.py,sha256=TAQqlPeJfOdH2MT-X7g3j1Se_0e4oKvG0tJaWC1kM40,381
|
|
26
|
-
furu/execution/slurm_dag.py,sha256=
|
|
27
|
-
furu/execution/slurm_pool.py,sha256=
|
|
28
|
-
furu/execution/slurm_spec.py,sha256=
|
|
26
|
+
furu/execution/slurm_dag.py,sha256=xh9EUGdPZaAH3UfcRqo6MsKYBIV-UW3_7owY8kLOwz4,9392
|
|
27
|
+
furu/execution/slurm_pool.py,sha256=ft76Gp-HgFWWjGvDclUChLOjY1rvhhfkP5mxhK3ViQk,30395
|
|
28
|
+
furu/execution/slurm_spec.py,sha256=DG8BF4FCga2ZXsqGUvfNibk6II40JcShVZ4jTwxTdec,977
|
|
29
29
|
furu/execution/submitit_factory.py,sha256=B2vkDtmscuAX0sBaj9V5pNlgOtkkV35yJ1fZ7A-DSvU,1119
|
|
30
30
|
furu/migrate.py,sha256=x_Uh7oXAv40L5ZAHJhdnw-o7ct56rWUSZLbHHfRObeY,1313
|
|
31
|
-
furu/migration.py,sha256=
|
|
31
|
+
furu/migration.py,sha256=EYWULuH8lEVvESthO2qEF95WJTo1Uj6d4L6VU2zmWpw,31350
|
|
32
32
|
furu/runtime/__init__.py,sha256=fQqE7wUuWunLD73Vm3lss7BFSij3UVxXOKQXBAOS8zw,504
|
|
33
|
-
furu/runtime/env.py,sha256=
|
|
34
|
-
furu/runtime/logging.py,sha256=
|
|
33
|
+
furu/runtime/env.py,sha256=lb-LWl-1EM_CP8sy0z3HAY20NXQ-v3QdOgte1i0HYVA,214
|
|
34
|
+
furu/runtime/logging.py,sha256=Xni1hWyH21bKc6D2owBZzThsj6q8yQOBD9zUrDS4jtI,10760
|
|
35
|
+
furu/runtime/overrides.py,sha256=E3fsZ0ReNOnC9xioHHFlmudm5K2DZLFFcEIvrnA6t2o,871
|
|
35
36
|
furu/runtime/tracebacks.py,sha256=PGCuOq8QkWSoun791gjUXM8frOP2wWV8IBlqaA4nuGE,1631
|
|
36
37
|
furu/serialization/__init__.py,sha256=L7oHuIbxdSh7GCY3thMQnDwlt_ERH-TMy0YKEAZLrPs,341
|
|
37
38
|
furu/serialization/migrations.py,sha256=HD5g8JCBdH3Y0rHJYc4Ug1IXBVcUDxLE7nfiXZnXcUE,7772
|
|
38
39
|
furu/serialization/serializer.py,sha256=_nfUaAOy_KHegvfXlpPh4rCuvkzalJva75OvDg5nXiI,10114
|
|
39
40
|
furu/storage/__init__.py,sha256=cLLL-GPpSu9C72Mdk5S6TGu3g-SnBfEuxzfpx5ZJPtw,616
|
|
40
|
-
furu/storage/metadata.py,sha256=
|
|
41
|
-
furu/storage/migration.py,sha256=
|
|
42
|
-
furu/storage/state.py,sha256=
|
|
43
|
-
furu
|
|
44
|
-
furu-0.0.
|
|
45
|
-
furu-0.0.
|
|
46
|
-
furu-0.0.
|
|
41
|
+
furu/storage/metadata.py,sha256=V16aePXsVo4qIBsKCVtNQvZDpMl0AcCCD2Fr-f-Q75I,9659
|
|
42
|
+
furu/storage/migration.py,sha256=FNExLdPu1ekKZR2XJkAgags9U8pV2FfkKAECSXkSra8,2585
|
|
43
|
+
furu/storage/state.py,sha256=kcIfAwdKWT8Q2ElbC5qofQC6noS_k6eNSPkNAdYXoaY,43707
|
|
44
|
+
furu/testing.py,sha256=lS-30bOu_RI1l4OV4lGWNpx5HOAwX2JYHHqakOkz8so,7804
|
|
45
|
+
furu-0.0.6.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
46
|
+
furu-0.0.6.dist-info/entry_points.txt,sha256=hZkjtFzNlb33Zk-aUfLMRj-XgVDxdT82-JXG9d4bu2E,60
|
|
47
|
+
furu-0.0.6.dist-info/METADATA,sha256=Z22LYfC7htpUg9VleX9vYA-GlruCYyycpe8VTpm5aYQ,19117
|
|
48
|
+
furu-0.0.6.dist-info/RECORD,,
|
|
File without changes
|