furu 0.0.6__py3-none-any.whl → 0.0.7__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/__init__.py +3 -15
- furu/aliases.py +53 -0
- furu/core/furu.py +153 -5
- furu/dashboard/api/models.py +18 -4
- furu/dashboard/api/routes.py +8 -0
- furu/dashboard/frontend/dist/assets/{index-BjyrY-Zz.js → index-NiDdQnqO.js} +15 -15
- furu/dashboard/frontend/dist/index.html +1 -1
- furu/dashboard/scanner.py +173 -147
- furu/migration.py +491 -763
- furu/schema.py +46 -0
- furu/storage/metadata.py +17 -1
- {furu-0.0.6.dist-info → furu-0.0.7.dist-info}/METADATA +1 -1
- {furu-0.0.6.dist-info → furu-0.0.7.dist-info}/RECORD +15 -14
- furu/migrate.py +0 -48
- {furu-0.0.6.dist-info → furu-0.0.7.dist-info}/WHEEL +0 -0
- {furu-0.0.6.dist-info → furu-0.0.7.dist-info}/entry_points.txt +0 -0
furu/schema.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import chz
|
|
4
|
+
|
|
5
|
+
from .serialization.serializer import JsonValue
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def schema_key_from_furu_obj(furu_obj: dict[str, JsonValue]) -> tuple[str, ...]:
|
|
9
|
+
if not isinstance(furu_obj, dict):
|
|
10
|
+
raise TypeError(f"schema_key requires dict furu_obj, got {type(furu_obj)}")
|
|
11
|
+
keys: set[str] = set()
|
|
12
|
+
for key in furu_obj:
|
|
13
|
+
if not isinstance(key, str):
|
|
14
|
+
raise TypeError(f"schema_key requires string keys, got {type(key)}")
|
|
15
|
+
if key.startswith("_"):
|
|
16
|
+
continue
|
|
17
|
+
keys.add(key)
|
|
18
|
+
return tuple(sorted(keys))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def schema_key_from_metadata_raw(metadata: dict[str, JsonValue]) -> tuple[str, ...]:
|
|
22
|
+
raw = metadata.get("schema_key")
|
|
23
|
+
if raw is None:
|
|
24
|
+
raise ValueError("metadata missing schema_key")
|
|
25
|
+
if isinstance(raw, tuple):
|
|
26
|
+
items = raw
|
|
27
|
+
elif isinstance(raw, list):
|
|
28
|
+
items = raw
|
|
29
|
+
else:
|
|
30
|
+
raise TypeError(f"metadata schema_key must be list or tuple, got {type(raw)}")
|
|
31
|
+
keys: list[str] = []
|
|
32
|
+
for item in items:
|
|
33
|
+
if not isinstance(item, str):
|
|
34
|
+
raise TypeError(f"metadata schema_key must be strings, got {type(item)}")
|
|
35
|
+
keys.append(item)
|
|
36
|
+
return tuple(keys)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def schema_key_from_cls(cls: type) -> tuple[str, ...]:
|
|
40
|
+
fields = chz.chz_fields(cls)
|
|
41
|
+
keys = {
|
|
42
|
+
field.logical_name
|
|
43
|
+
for field in fields.values()
|
|
44
|
+
if not field.logical_name.startswith("_")
|
|
45
|
+
}
|
|
46
|
+
return tuple(sorted(keys))
|
furu/storage/metadata.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import getpass
|
|
3
|
+
import importlib.metadata
|
|
3
4
|
import json
|
|
4
5
|
import os
|
|
5
6
|
import platform
|
|
@@ -9,9 +10,10 @@ import sys
|
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import TYPE_CHECKING
|
|
11
12
|
|
|
12
|
-
from pydantic import BaseModel, ConfigDict
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
13
14
|
|
|
14
15
|
from ..config import FURU_CONFIG
|
|
16
|
+
from ..schema import schema_key_from_furu_obj
|
|
15
17
|
from ..serialization import BaseModel as PydanticBaseModel
|
|
16
18
|
from ..serialization import FuruSerializer
|
|
17
19
|
from ..serialization.serializer import JsonValue
|
|
@@ -50,6 +52,7 @@ class EnvironmentInfo(BaseModel):
|
|
|
50
52
|
|
|
51
53
|
timestamp: str
|
|
52
54
|
command: str
|
|
55
|
+
furu_package_version: str | None = None
|
|
53
56
|
python_version: str
|
|
54
57
|
executable: str
|
|
55
58
|
platform: str
|
|
@@ -66,6 +69,7 @@ class FuruMetadata(BaseModel):
|
|
|
66
69
|
# Furu-specific fields
|
|
67
70
|
furu_python_def: str
|
|
68
71
|
furu_obj: JsonValue # Serialized Furu object from FuruSerializer.to_dict()
|
|
72
|
+
schema_key: tuple[str, ...]
|
|
69
73
|
furu_hash: str
|
|
70
74
|
furu_path: str
|
|
71
75
|
|
|
@@ -79,6 +83,7 @@ class FuruMetadata(BaseModel):
|
|
|
79
83
|
# Environment info
|
|
80
84
|
timestamp: str
|
|
81
85
|
command: str
|
|
86
|
+
furu_package_version: str | None = None
|
|
82
87
|
python_version: str
|
|
83
88
|
executable: str
|
|
84
89
|
platform: str
|
|
@@ -86,6 +91,13 @@ class FuruMetadata(BaseModel):
|
|
|
86
91
|
user: str
|
|
87
92
|
pid: int
|
|
88
93
|
|
|
94
|
+
@field_validator("schema_key", mode="before")
|
|
95
|
+
@classmethod
|
|
96
|
+
def _coerce_schema_key(cls, value: object) -> object:
|
|
97
|
+
if isinstance(value, list):
|
|
98
|
+
return tuple(value)
|
|
99
|
+
return value
|
|
100
|
+
|
|
89
101
|
|
|
90
102
|
class MetadataManager:
|
|
91
103
|
"""Handles metadata collection and storage."""
|
|
@@ -217,6 +229,7 @@ class MetadataManager:
|
|
|
217
229
|
timespec="microseconds"
|
|
218
230
|
),
|
|
219
231
|
command=" ".join(sys.argv) if sys.argv else "<unknown>",
|
|
232
|
+
furu_package_version=importlib.metadata.version("furu"),
|
|
220
233
|
python_version=sys.version,
|
|
221
234
|
executable=sys.executable,
|
|
222
235
|
platform=platform.platform(),
|
|
@@ -238,10 +251,12 @@ class MetadataManager:
|
|
|
238
251
|
raise TypeError(
|
|
239
252
|
f"Expected FuruSerializer.to_dict to return dict, got {type(serialized_obj)}"
|
|
240
253
|
)
|
|
254
|
+
schema_key = schema_key_from_furu_obj(serialized_obj)
|
|
241
255
|
|
|
242
256
|
return FuruMetadata(
|
|
243
257
|
furu_python_def=FuruSerializer.to_python(furu_obj, multiline=False),
|
|
244
258
|
furu_obj=serialized_obj,
|
|
259
|
+
schema_key=schema_key,
|
|
245
260
|
furu_hash=FuruSerializer.compute_hash(furu_obj),
|
|
246
261
|
furu_path=str(directory.resolve()),
|
|
247
262
|
git_commit=git_info.git_commit,
|
|
@@ -251,6 +266,7 @@ class MetadataManager:
|
|
|
251
266
|
git_submodules=git_info.git_submodules,
|
|
252
267
|
timestamp=env_info.timestamp,
|
|
253
268
|
command=env_info.command,
|
|
269
|
+
furu_package_version=env_info.furu_package_version,
|
|
254
270
|
python_version=env_info.python_version,
|
|
255
271
|
executable=env_info.executable,
|
|
256
272
|
platform=env_info.platform,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: furu
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
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>
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
furu/__init__.py,sha256=
|
|
1
|
+
furu/__init__.py,sha256=umlKxb3zB5KYsx3meqLFzqNRduGfEVUplTZSelX-p4E,1729
|
|
2
2
|
furu/adapters/__init__.py,sha256=onLzEj9hccPK15g8a8va2T19nqQXoxb9rQlJIjKSKnE,69
|
|
3
3
|
furu/adapters/submitit.py,sha256=FV3XEUSQuS5vIyzkW-Iuqtf8SRL-fsokPG67u7tMF5I,7276
|
|
4
|
+
furu/aliases.py,sha256=C31Oe2w8gTAuVjo-eKCs_Xp3Ir16HRyn327CeczuO6c,1755
|
|
4
5
|
furu/config.py,sha256=UGnH8QAKMUgrGMGNkfBgLXideXEpDlozUSsX9iNN8Lw,6844
|
|
5
6
|
furu/core/__init__.py,sha256=6hH7i6r627c0FZn6eQVsSG7LD4QmTta6iQw0AiPQPTM,156
|
|
6
|
-
furu/core/furu.py,sha256=
|
|
7
|
+
furu/core/furu.py,sha256=PBz9_gykog9J-QY45pt99DR5f03ssFys1DhiEPBRAQA,66692
|
|
7
8
|
furu/core/list.py,sha256=QaGSh8NFg1K2WFncM8duOYQ6KLZ6EW2pRLArN_e5Juw,3662
|
|
8
9
|
furu/dashboard/__init__.py,sha256=ziAordJfkbbXNIM7iA9O7vR2gsCq34AInYiMYOCfWOc,362
|
|
9
10
|
furu/dashboard/__main__.py,sha256=cNs65IMl4kwZFpxa9xLXmFSy4-M5D1X1ZBfTDxW11vo,144
|
|
10
11
|
furu/dashboard/api/__init__.py,sha256=9-WyWOt-VQJJBIsdW29D-7JvR-BivJd9G_SRaRptCz0,80
|
|
11
|
-
furu/dashboard/api/models.py,sha256=
|
|
12
|
-
furu/dashboard/api/routes.py,sha256=
|
|
12
|
+
furu/dashboard/api/models.py,sha256=ptC1NKMl2zGb8ERR7JY_EQBi-Zxc4NvNhnFxcOsBawk,5133
|
|
13
|
+
furu/dashboard/api/routes.py,sha256=TCGTceH-i7dv1Y6JE4JFBcmyOs6QTTDRp5zdjwuBpPQ,5035
|
|
13
14
|
furu/dashboard/frontend/dist/assets/index-BXAIKNNr.css,sha256=qhsN0Td3mM-GAR8mZ0CtocynABLKa1ncl9ioDrTKOIQ,34768
|
|
14
|
-
furu/dashboard/frontend/dist/assets/index-
|
|
15
|
+
furu/dashboard/frontend/dist/assets/index-NiDdQnqO.js,sha256=Ns1BosYU5xckMAn_b4iUtD6-m7tB9r3GY7_rwFgzumI,545103
|
|
15
16
|
furu/dashboard/frontend/dist/favicon.svg,sha256=3TSLHNZITFe3JTPoYHZnDgiGsJxIzf39v97l2A1Hodo,369
|
|
16
|
-
furu/dashboard/frontend/dist/index.html,sha256=
|
|
17
|
+
furu/dashboard/frontend/dist/index.html,sha256=i7eDqiisdL86W0ZldsUg3z5wsX1NfH98adPxgh0p06Q,810
|
|
17
18
|
furu/dashboard/main.py,sha256=gj9Cdj2qyaSCEkmfNHUMQXlXv6GpWTQ9IZEi7WzlCSo,4463
|
|
18
|
-
furu/dashboard/scanner.py,sha256=
|
|
19
|
+
furu/dashboard/scanner.py,sha256=CMLcUTZ2mvaigpMM1qbonpPwvG8Om_FIVd11PruSZFo,35467
|
|
19
20
|
furu/errors.py,sha256=FFbV4M0-ipVGizv5ee80L-NZFVjaRjy8i19mClr6R0g,3959
|
|
20
21
|
furu/execution/__init__.py,sha256=ixVw1Shvg2ulS597OYYeGgSSTwv25j_McuQdDXIiEL8,625
|
|
21
22
|
furu/execution/context.py,sha256=0tAbM0azqEus8hknf_A9-Zs9Sq99bnUkFyV4RO4ZMRU,666
|
|
@@ -27,22 +28,22 @@ furu/execution/slurm_dag.py,sha256=xh9EUGdPZaAH3UfcRqo6MsKYBIV-UW3_7owY8kLOwz4,9
|
|
|
27
28
|
furu/execution/slurm_pool.py,sha256=ft76Gp-HgFWWjGvDclUChLOjY1rvhhfkP5mxhK3ViQk,30395
|
|
28
29
|
furu/execution/slurm_spec.py,sha256=DG8BF4FCga2ZXsqGUvfNibk6II40JcShVZ4jTwxTdec,977
|
|
29
30
|
furu/execution/submitit_factory.py,sha256=B2vkDtmscuAX0sBaj9V5pNlgOtkkV35yJ1fZ7A-DSvU,1119
|
|
30
|
-
furu/
|
|
31
|
-
furu/migration.py,sha256=EYWULuH8lEVvESthO2qEF95WJTo1Uj6d4L6VU2zmWpw,31350
|
|
31
|
+
furu/migration.py,sha256=OG-GvDscZYNY0Mz8nQJ-F7tWW9Ev6iUwTKOyc6uwBpc,21058
|
|
32
32
|
furu/runtime/__init__.py,sha256=fQqE7wUuWunLD73Vm3lss7BFSij3UVxXOKQXBAOS8zw,504
|
|
33
33
|
furu/runtime/env.py,sha256=lb-LWl-1EM_CP8sy0z3HAY20NXQ-v3QdOgte1i0HYVA,214
|
|
34
34
|
furu/runtime/logging.py,sha256=Xni1hWyH21bKc6D2owBZzThsj6q8yQOBD9zUrDS4jtI,10760
|
|
35
35
|
furu/runtime/overrides.py,sha256=E3fsZ0ReNOnC9xioHHFlmudm5K2DZLFFcEIvrnA6t2o,871
|
|
36
36
|
furu/runtime/tracebacks.py,sha256=PGCuOq8QkWSoun791gjUXM8frOP2wWV8IBlqaA4nuGE,1631
|
|
37
|
+
furu/schema.py,sha256=t-XyapYJpYQEmLFo-PGwrCUzfAG-WefTi3OYbveRp0Y,1453
|
|
37
38
|
furu/serialization/__init__.py,sha256=L7oHuIbxdSh7GCY3thMQnDwlt_ERH-TMy0YKEAZLrPs,341
|
|
38
39
|
furu/serialization/migrations.py,sha256=HD5g8JCBdH3Y0rHJYc4Ug1IXBVcUDxLE7nfiXZnXcUE,7772
|
|
39
40
|
furu/serialization/serializer.py,sha256=_nfUaAOy_KHegvfXlpPh4rCuvkzalJva75OvDg5nXiI,10114
|
|
40
41
|
furu/storage/__init__.py,sha256=cLLL-GPpSu9C72Mdk5S6TGu3g-SnBfEuxzfpx5ZJPtw,616
|
|
41
|
-
furu/storage/metadata.py,sha256=
|
|
42
|
+
furu/storage/metadata.py,sha256=EYT4sRSFz2KY3nlc4DAJBfxuvi2rQedPjPK_NGJlQ3k,10313
|
|
42
43
|
furu/storage/migration.py,sha256=FNExLdPu1ekKZR2XJkAgags9U8pV2FfkKAECSXkSra8,2585
|
|
43
44
|
furu/storage/state.py,sha256=kcIfAwdKWT8Q2ElbC5qofQC6noS_k6eNSPkNAdYXoaY,43707
|
|
44
45
|
furu/testing.py,sha256=lS-30bOu_RI1l4OV4lGWNpx5HOAwX2JYHHqakOkz8so,7804
|
|
45
|
-
furu-0.0.
|
|
46
|
-
furu-0.0.
|
|
47
|
-
furu-0.0.
|
|
48
|
-
furu-0.0.
|
|
46
|
+
furu-0.0.7.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
47
|
+
furu-0.0.7.dist-info/entry_points.txt,sha256=hZkjtFzNlb33Zk-aUfLMRj-XgVDxdT82-JXG9d4bu2E,60
|
|
48
|
+
furu-0.0.7.dist-info/METADATA,sha256=v6gNqvq19H4H9WPMl2PcaDIOZVP1Qtz1lQfzBMzN0Yc,19117
|
|
49
|
+
furu-0.0.7.dist-info/RECORD,,
|
furu/migrate.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Literal, Mapping
|
|
4
|
-
|
|
5
|
-
from .core.furu import Furu
|
|
6
|
-
from .migration import (
|
|
7
|
-
MigrationValue,
|
|
8
|
-
apply_migration,
|
|
9
|
-
find_migration_candidates_initialized_target,
|
|
10
|
-
)
|
|
11
|
-
from .storage import MigrationRecord
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
MigrationPolicy = Literal["alias", "move", "copy"]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def migrate(
|
|
18
|
-
from_obj: Furu,
|
|
19
|
-
to_obj: Furu,
|
|
20
|
-
*,
|
|
21
|
-
policy: MigrationPolicy = "alias",
|
|
22
|
-
origin: str | None = None,
|
|
23
|
-
note: str | None = None,
|
|
24
|
-
default_values: Mapping[str, MigrationValue] | None = None,
|
|
25
|
-
) -> MigrationRecord:
|
|
26
|
-
from_namespace = ".".join(from_obj._namespace().parts)
|
|
27
|
-
candidates = find_migration_candidates_initialized_target(
|
|
28
|
-
to_obj=to_obj,
|
|
29
|
-
from_namespace=from_namespace,
|
|
30
|
-
default_fields=None,
|
|
31
|
-
drop_fields=None,
|
|
32
|
-
)
|
|
33
|
-
if not candidates:
|
|
34
|
-
raise ValueError("migration: no candidates found for initialized target")
|
|
35
|
-
if len(candidates) != 1:
|
|
36
|
-
raise ValueError("migration: expected exactly one candidate")
|
|
37
|
-
candidate = candidates[0]
|
|
38
|
-
if default_values:
|
|
39
|
-
candidate = candidate.with_default_values(default_values)
|
|
40
|
-
records = apply_migration(
|
|
41
|
-
candidate,
|
|
42
|
-
policy=policy,
|
|
43
|
-
cascade=True,
|
|
44
|
-
origin=origin,
|
|
45
|
-
note=note,
|
|
46
|
-
conflict="throw",
|
|
47
|
-
)
|
|
48
|
-
return records[0]
|
|
File without changes
|
|
File without changes
|