from __future__ import annotations import hashlib import io import re from dataclasses import dataclass @dataclass(frozen=True) class ArtifactRecord: uid: str source_uid: str filename: str media_type: str size_bytes: int content_hash: str storage_ref: str parser_version: str class ArtifactService: def __init__(self, repository, storage, file_policy, *, uid_factory): self.repository = repository self.storage = storage self.file_policy = file_policy self.uid_factory = uid_factory def store(self, source_uid, filename, media_type, content, parser_version): validated = self.file_policy.validate(filename, media_type, content) digest = hashlib.sha256(content).hexdigest() existing = self.repository.find(source_uid, digest, parser_version) if existing is not None: return existing, False object_key = f"data-research/{source_uid}/{digest}" storage_ref = self.storage.put(object_key, content, validated.media_type) record = ArtifactRecord( uid=self.uid_factory(), source_uid=str(source_uid), filename=validated.filename, media_type=validated.media_type, size_bytes=validated.size_bytes, content_hash=digest, storage_ref=storage_ref, parser_version=str(parser_version), ) return self.repository.save(record), True def redact_excerpt(value): value = re.sub(r"(?