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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: furu
3
- Version: 0.0.4
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
- | `FURU_IGNORE_DIFF` | `false` | Skip embedding git diff in metadata |
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
- | `FURU_RICH_UNCAUGHT_TRACEBACKS` | `true` | Use Rich for exception formatting |
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 if `python-dotenv` is installed.
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.ignore_git_diff = True
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=1nlJff4KNrWDvLhmnuLrsc7FJIxFLFhz3eOXZ8-ngX4,7349
4
+ furu/config.py,sha256=UGnH8QAKMUgrGMGNkfBgLXideXEpDlozUSsX9iNN8Lw,6844
5
5
  furu/core/__init__.py,sha256=6hH7i6r627c0FZn6eQVsSG7LD4QmTta6iQw0AiPQPTM,156
6
- furu/core/furu.py,sha256=Cy2cOnM5vsQoSk9nIVYj2Fx017wOQFPbxhnvYQsh7nI,58881
7
- furu/core/list.py,sha256=xSuBT35p1anJ2fKQPxb-3cRTONUamFjfzkreVaI9Jo4,3614
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-DS3FsqcY.js,sha256=nfrKjhWThPtL8n5iTd9_1W-bsyMGwg2O8Iq2jkjj9Lg,544699
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=d9a8ZFKZ5uDtN3urqVNmS8LWMBhOC0eW7X0noT0RcYQ,810
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=TkKrRdmaQrN7i7Sxe87eHibRJOnz5OxU0Oj8qL_xP4I,7059
22
+ furu/execution/local.py,sha256=SXUH9PfcCAeHSZYrTP1YNjl2fV7vqzpZXZQzmtIcVMg,7137
23
23
  furu/execution/paths.py,sha256=0MfQk5Kh7bxvJiWvG40TJe7RF5Q5Na6uvi6qV0OT3Vc,460
24
- furu/execution/plan.py,sha256=fM7CkXm_M0lL3vqdiNnWzbvMJAoSYKDBAnC82Af_rYM,6860
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=FOJcPKmIzRyrbJIq7heqGjKN0EFRMyOcV-yP7Ci87Qs,9360
27
- furu/execution/slurm_pool.py,sha256=bi90fzZXAnoWHSPQba8Z3tk4_QMaqikWxCCzRfvDMvk,30400
28
- furu/execution/slurm_spec.py,sha256=A1VX5K6aG8Ricg4fhnkz3Alkw_fx1bx53D0p4Ms3FqA,979
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=R2-tARMx4VKryiqJ7WHia_dPVxRbTqofPpCFVE9zQ8U,31411
31
+ furu/migration.py,sha256=EYWULuH8lEVvESthO2qEF95WJTo1Uj6d4L6VU2zmWpw,31350
32
32
  furu/runtime/__init__.py,sha256=fQqE7wUuWunLD73Vm3lss7BFSij3UVxXOKQXBAOS8zw,504
33
- furu/runtime/env.py,sha256=o1phhoTDhOnhALr3Ozf1ldrdvk2ClyEvBWbebHM6BXg,160
34
- furu/runtime/logging.py,sha256=WS3mB8VqMYUxPPI0yv1K-LnzVBj84Mnu1Qf9P2hCUUE,9652
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=MH6w5hs-2rwHD6G9erMPM5pE3hm0h5Pk_G3Z6eyyGB0,9899
41
- furu/storage/migration.py,sha256=Ars9aYwvhXpIBDf6L9ojGjp_l656-RfdtEAFKN0sZZY,2640
42
- furu/storage/state.py,sha256=SFonqragT2eMCZbBKIvcA4JVe78rVmDRvo4Ky2IcNgc,43632
43
- furu-0.0.4.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
44
- furu-0.0.4.dist-info/entry_points.txt,sha256=hZkjtFzNlb33Zk-aUfLMRj-XgVDxdT82-JXG9d4bu2E,60
45
- furu-0.0.4.dist-info/METADATA,sha256=fdUBvn-vEnVim9V5hAamE1sFuaKzWdwWPI17VU2Vyfc,16162
46
- furu-0.0.4.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.28
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any