Skip to content

Commit 5fd60e9

Browse files
committed
Record source artifact hash during import
1 parent 01cd66a commit 5fd60e9

3 files changed

Lines changed: 15 additions & 0 deletions

File tree

‎src/openstatspec/spss/sav.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Initial unencrypted SAV/ZSAV adapter using pyreadstat."""
22

3+
import hashlib
34
import json
45
import math
56
from pathlib import Path
@@ -72,6 +73,7 @@ def import_sav_dataset(*, source: str | Path, database_url: str, dataset_id: str
7273
source_format=source_path.suffix[1:].upper(), rows=_rows(frame, variables),
7374
variables=variables, file_label=getattr(meta, "file_label", "") or "",
7475
source_encoding=getattr(meta, "file_encoding", None),
76+
source_sha256=_sha256(source_path),
7577
documents=json.dumps(list(getattr(meta, "notes", []) or [])),
7678
multiple_response_sets=json.dumps(dict(getattr(meta, "mr_sets", {}) or {}), default=str),
7779
)
@@ -114,6 +116,14 @@ def export_sav_dataset(*, database_url: str, dataset_id: str, destination: str |
114116

115117

116118

119+
120+
def _sha256(path: Path) -> str:
121+
digest = hashlib.sha256()
122+
with path.open("rb") as source:
123+
for chunk in iter(lambda: source.read(1024 * 1024), b""):
124+
digest.update(chunk)
125+
return digest.hexdigest()
126+
117127
def _import_loss_report(meta: Any) -> tuple[dict[str, str], ...]:
118128
if getattr(meta, "mr_sets", {}) or {}:
119129
return ({"code": "multiple-response-sets-not-exportable", "detail": "Multiple-response sets are catalogued but pyreadstat cannot write them back to SAV."},)

‎src/openstatspec/sql/wide.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def catalog(metadata: MetaData) -> tuple[Table, Table]:
1717
Column("data_table", String(255), nullable=False, unique=True),
1818
Column("source_format", String(16), nullable=False),
1919
Column("source_name", Text, nullable=False),
20+
Column("source_sha256", String(64), nullable=False),
2021
Column("source_encoding", String(128)),
2122
Column("case_count", BigInteger, nullable=False),
2223
Column("file_label", Text, nullable=False, default=""),
@@ -62,6 +63,7 @@ def create_wide_dataset(
6263
*, database_url: str, dataset_id: str, source_name: str, source_format: str,
6364
rows: Iterable[Mapping[str, Any]], variables: list[dict[str, Any]], file_label: str = "",
6465
source_encoding: str | None = None, documents: str = "[]",
66+
source_sha256: str = "",
6567
multiple_response_sets: str = "{}",
6668
) -> dict[str, Any]:
6769
profile = profile_for_url(database_url)
@@ -84,6 +86,7 @@ def create_wide_dataset(
8486
connection.execute(insert(datasets).values(
8587
dataset_id=dataset_id, data_table=data_table.name, source_format=source_format,
8688
source_name=source_name, source_encoding=source_encoding, case_count=len(materialized),
89+
source_sha256=source_sha256,
8790
file_label=file_label, documents=documents,
8891
multiple_response_sets=multiple_response_sets,
8992
))

‎tests/test_sav_sqlite.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
import json
23
import sqlite3
34

@@ -35,6 +36,7 @@ def test_sav_round_trip_uses_one_wide_table_and_catalog(tmp_path) -> None:
3536
(1, 34.0, "Ada"), (2, None, "")
3637
]
3738
assert connection.execute("select source_encoding, documents from dataset_catalog").fetchone() == ("UTF-8", json.dumps(["Fixture note"]))
39+
assert connection.execute("select source_sha256 from dataset_catalog").fetchone() == (hashlib.sha256(source.read_bytes()).hexdigest(),)
3840

3941
openstatspec.export_sav(database_url=database, dataset_id="tiny", destination=exported)
4042
frame, meta = pyreadstat.read_sav(exported, user_missing=True)

0 commit comments

Comments
 (0)