suvradeepp's picture
Publish Tiny Hinglish Turn Detector development preview
35d483e verified
Raw
History Blame Contribute Delete
3.49 kB
"""Portable JSONL manifest I/O.
Audit manifests contain the following stable keys:
``record_id, source_file, source_row, language, endpoint, midfiller,
endfiller, synthetic, dataset, audio_path, audio_sha256, audio_num_bytes,
audio_format, sample_rate, num_channels, num_frames, bits_per_sample,
duration_seconds, spoken_text, group_keys, group_id, validation_errors,
validation_warnings``.
Split manifests retain those keys and add ``split``. Audio bytes are never
embedded in a manifest; ``source_file`` and ``source_row`` preserve provenance.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import tempfile
from typing import Any, Iterable, Iterator, Mapping
MANIFEST_SCHEMA_VERSION = "1.0"
def read_manifest(path: str | Path, *, limit: int | None = None) -> Iterator[dict[str, Any]]:
"""Stream a JSONL manifest using only the Python standard library."""
if limit is not None and limit < 0:
raise ValueError("limit cannot be negative")
manifest_path = Path(path)
with manifest_path.open("r", encoding="utf-8") as handle:
emitted = 0
for line_number, line in enumerate(handle, start=1):
if not line.strip():
continue
if limit is not None and emitted >= limit:
return
try:
value = json.loads(line)
except json.JSONDecodeError as exc:
raise ValueError(f"invalid JSON at {manifest_path}:{line_number}: {exc}") from exc
if not isinstance(value, dict):
raise ValueError(f"manifest row at {manifest_path}:{line_number} is not an object")
yield value
emitted += 1
def write_manifest(path: str | Path, rows: Iterable[Mapping[str, Any]]) -> int:
"""Atomically write JSONL rows and return the number written."""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{output_path.name}.", suffix=".tmp", dir=str(output_path.parent)
)
count = 0
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
for row in rows:
handle.write(json.dumps(dict(row), sort_keys=True, ensure_ascii=False, allow_nan=False))
handle.write("\n")
count += 1
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary_name, output_path)
except BaseException:
try:
os.unlink(temporary_name)
except FileNotFoundError:
pass
raise
return count
def write_json(path: str | Path, payload: Mapping[str, Any]) -> None:
"""Atomically write a deterministic UTF-8 JSON document."""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{output_path.name}.", suffix=".tmp", dir=str(output_path.parent)
)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
json.dump(dict(payload), handle, indent=2, sort_keys=True, ensure_ascii=False, allow_nan=False)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary_name, output_path)
except BaseException:
try:
os.unlink(temporary_name)
except FileNotFoundError:
pass
raise