Skip to content

Commit 68b9bee

Browse files
committed
fix: some tweaks
1 parent 332bf32 commit 68b9bee

8 files changed

Lines changed: 348 additions & 139 deletions

File tree

src/fluxel/core/SPEC.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Spec (Human Authored)
2+

src/fluxel/core/index.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
from __future__ import annotations
99

10+
import csv
1011
from dataclasses import dataclass
1112
from pathlib import Path
1213
from tempfile import NamedTemporaryFile
1314

1415
import duckdb
1516

16-
from .manifest import ManifestWriter
1717
from .repository import open_repository
1818

1919

@@ -43,13 +43,24 @@ def build_analytical_index(
4343
db_path = index_root / f"{commit_id}.duckdb"
4444

4545
with NamedTemporaryFile(
46-
mode="w", suffix=".jsonl", delete=False, encoding="utf-8"
46+
mode="w", suffix=".csv", delete=False, encoding="utf-8", newline=""
4747
) as temp:
4848
manifest_path = Path(temp.name)
4949
try:
50-
ManifestWriter(manifest_path).write_entries(
51-
repo.store.iter_manifest_entries(commit.manifest)
52-
)
50+
with manifest_path.open("w", encoding="utf-8", newline="") as handle:
51+
writer = csv.writer(handle)
52+
writer.writerow(["path", "hash", "size", "mtime_ns", "commit_id", "branch"])
53+
for entry in repo.store.iter_manifest_entries(commit.manifest):
54+
writer.writerow(
55+
[
56+
entry.path,
57+
entry.hash,
58+
entry.size,
59+
entry.mtime_ns,
60+
commit_id,
61+
commit.branch,
62+
]
63+
)
5364

5465
conn = duckdb.connect(str(db_path))
5566
try:
@@ -61,11 +72,11 @@ def build_analytical_index(
6172
hash::VARCHAR AS hash,
6273
size::BIGINT AS size,
6374
mtime_ns::BIGINT AS mtime_ns,
64-
?::VARCHAR AS commit_id,
65-
?::VARCHAR AS branch
66-
FROM read_json_auto(?, format='newline_delimited')
75+
commit_id::VARCHAR AS commit_id,
76+
branch::VARCHAR AS branch
77+
FROM read_csv_auto(?, header=true)
6778
""",
68-
[commit_id, commit.branch, str(manifest_path)],
79+
[str(manifest_path)],
6980
)
7081
conn.execute("CREATE INDEX IF NOT EXISTS idx_files_path ON files(path)")
7182
conn.execute("CREATE INDEX IF NOT EXISTS idx_files_size ON files(size)")

src/fluxel/core/manifest.py

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from __future__ import annotations
99

1010
import json
11-
import os
12-
from dataclasses import asdict, dataclass
11+
from dataclasses import dataclass
1312
from json import JSONDecodeError
1413
from pathlib import Path
1514
from pathlib import PurePosixPath
@@ -21,6 +20,8 @@
2120
SUPPORTED_IDENTITY_MODES = frozenset({"blake3", "meta"})
2221
_BLAKE3_HEX_LENGTH = 64
2322
_HEX_DIGITS = frozenset("0123456789abcdef")
23+
_BLOB_BACKED_MANIFEST_TAG = "b"
24+
_META_ONLY_MANIFEST_TAG = "m"
2425

2526

2627
def _is_hex_digest(value: str) -> bool:
@@ -144,6 +145,86 @@ def from_dict(data: dict[str, object]) -> "ManifestEntry":
144145
)
145146

146147

148+
def serialize_manifest_entry(entry: ManifestEntry) -> str:
149+
if entry.identity_mode == "blake3":
150+
payload: list[object] = [
151+
_BLOB_BACKED_MANIFEST_TAG,
152+
entry.path,
153+
entry.hash,
154+
entry.size,
155+
entry.mtime_ns,
156+
]
157+
elif entry.identity_mode == "meta":
158+
payload = [
159+
_META_ONLY_MANIFEST_TAG,
160+
entry.path,
161+
entry.hash,
162+
entry.size,
163+
entry.mtime_ns,
164+
entry.source_uri,
165+
]
166+
else:
167+
supported_modes = ", ".join(sorted(SUPPORTED_IDENTITY_MODES))
168+
raise ValueError(
169+
f"Manifest entry identity_mode must be one of: {supported_modes}"
170+
)
171+
return json.dumps(payload, separators=(",", ":"))
172+
173+
174+
def deserialize_manifest_entry(payload_text: str) -> ManifestEntry:
175+
try:
176+
payload = _load_manifest_payload(payload_text)
177+
except JSONDecodeError as error:
178+
raise ValueError("Corrupt manifest entry payload") from error
179+
180+
return _manifest_entry_from_payload(payload)
181+
182+
183+
def manifest_entry_path(payload_text: str) -> str:
184+
try:
185+
payload = _load_manifest_payload(payload_text)
186+
except JSONDecodeError as error:
187+
raise ValueError("Corrupt manifest entry payload") from error
188+
if not isinstance(payload, list) or len(payload) < 2:
189+
raise ValueError("Manifest entry payload must be a JSON array")
190+
return str(payload[1])
191+
192+
193+
def _load_manifest_payload(payload_text: str) -> object:
194+
return json.loads(payload_text)
195+
196+
197+
def _manifest_entry_from_payload(payload: object) -> ManifestEntry:
198+
199+
if not isinstance(payload, list):
200+
raise ValueError("Manifest entry payload must be a JSON array")
201+
if len(payload) == 5 and payload[0] == _BLOB_BACKED_MANIFEST_TAG:
202+
_, path, hash_value, size, mtime_ns = payload
203+
return ManifestEntry(
204+
path=str(path),
205+
hash=str(hash_value),
206+
size=int(size),
207+
mtime_ns=int(mtime_ns),
208+
identity_mode="blake3",
209+
identity_value=str(hash_value),
210+
blob_hash=str(hash_value),
211+
source_uri=None,
212+
)
213+
if len(payload) == 6 and payload[0] == _META_ONLY_MANIFEST_TAG:
214+
_, path, hash_value, size, mtime_ns, source_uri = payload
215+
return ManifestEntry(
216+
path=str(path),
217+
hash=str(hash_value),
218+
size=int(size),
219+
mtime_ns=int(mtime_ns),
220+
identity_mode="meta",
221+
identity_value=str(hash_value),
222+
blob_hash=None,
223+
source_uri=str(source_uri),
224+
)
225+
raise ValueError("Manifest entry payload has an unsupported shape")
226+
227+
147228
class ManifestWriter:
148229
def __init__(self, manifest_path: str | Path) -> None:
149230
self.manifest_path = Path(manifest_path)
@@ -153,7 +234,7 @@ def write_entries(self, entries: Iterable[ManifestEntry]) -> int:
153234
written = 0
154235
with self.manifest_path.open("w", encoding="utf-8") as handle:
155236
for entry in entries:
156-
handle.write(json.dumps(asdict(entry), separators=(",", ":")))
237+
handle.write(serialize_manifest_entry(entry))
157238
handle.write("\n")
158239
written += 1
159240
return written
@@ -184,13 +265,13 @@ def iter_entries(self) -> Iterator[ManifestEntry]:
184265
if not line:
185266
continue
186267
try:
187-
payload = json.loads(line)
268+
payload = _load_manifest_payload(line)
188269
except JSONDecodeError as error:
189270
raise ValueError(
190271
f"Corrupt manifest JSON at line {line_number} in {self.manifest_path}"
191272
) from error
192273
try:
193-
yield ManifestEntry.from_dict(payload)
274+
yield _manifest_entry_from_payload(payload)
194275
except ValueError as error:
195276
raise ValueError(
196277
f"Invalid manifest entry at line {line_number} in {self.manifest_path}: {error}"
@@ -230,7 +311,15 @@ def build_manifest_entries(
230311

231312
def walk_files(root: str | Path) -> Iterator[Path]:
232313
root_path = Path(root).resolve()
233-
for dirpath, dirnames, filenames in os.walk(root_path):
234-
dirnames[:] = [name for name in dirnames if name != ".fluxel"]
235-
for filename in filenames:
236-
yield Path(dirpath) / filename
314+
315+
def iter_dir(path: Path) -> Iterator[Path]:
316+
for child in sorted(path.iterdir(), key=lambda item: item.name):
317+
if child.name == ".fluxel":
318+
continue
319+
if child.is_dir():
320+
yield from iter_dir(child)
321+
continue
322+
if child.is_file():
323+
yield child
324+
325+
yield from iter_dir(root_path)

0 commit comments

Comments
 (0)