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/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.6
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=Z8VssTuQm2nH7bgB8SQc8pXsNGc-H1QGHFffKzNzqk8,2018
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=2KTeMjQjRoDaXNIoKUlYmJ0JWJFG4krom7w6UsUpvDg,61785
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=SCu-kLJyW7dwSKswdgQNS3wQuj25ORs0pHkvX9xBbo4,4767
12
- furu/dashboard/api/routes.py,sha256=iZez0khIUvbgfeSoy1BJvmoEEbgUrdSQA8SN8iAIkM8,4813
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-BjyrY-Zz.js,sha256=fItsQ--Dzobq5KdUcuqDi4txM2-NNqx8JET5Lwkwf7U,544515
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=Ig-j0qgTXBSge0GN7PaM7mcLnuRhRMQmkTZjU1wmTXY,810
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=qXCvkvFByBc09TUdth5Js67rS8zpRBlRkVQ9dJ7YbdE,34696
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/migrate.py,sha256=x_Uh7oXAv40L5ZAHJhdnw-o7ct56rWUSZLbHHfRObeY,1313
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=V16aePXsVo4qIBsKCVtNQvZDpMl0AcCCD2Fr-f-Q75I,9659
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.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,,
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