Spaces:
Running
Running
Download scripts/fetch_runtime_assets.py from gauravmeena0708/epfo-circulars: direct link, hf CLI and curl.
- Browser
- Download file 4.98 kB
-
https://huggingface.co/spaces/gauravmeena0708/epfo-circulars/resolve/main/scripts/fetch_runtime_assets.py
- Command line
-
hf download hf://spaces/gauravmeena0708/epfo-circulars/scripts/fetch_runtime_assets.py
-
curl -L -o fetch_runtime_assets.py https://huggingface.co/spaces/gauravmeena0708/epfo-circulars/resolve/main/scripts/fetch_runtime_assets.py
4.98 kB
| """Fetch immutable, hash-checked runtime data without growing Space Git history. | |
| Record after committing assets: python scripts/fetch_runtime_assets.py --record-commit SHA | |
| Docker build: python scripts/fetch_runtime_assets.py --output /assets/data | |
| """ | |
| import argparse | |
| from concurrent.futures import ThreadPoolExecutor | |
| import hashlib | |
| import json | |
| from pathlib import Path, PurePosixPath | |
| import re | |
| import tempfile | |
| import time | |
| import urllib.request | |
| REPO = 'gauravmeena0708/pf-circulars' | |
| MANIFEST = Path('deploy/runtime-assets.json') | |
| def digest(path): | |
| value = hashlib.sha256() | |
| with Path(path).open('rb') as stream: | |
| for block in iter(lambda: stream.read(1024 * 1024), b''): | |
| value.update(block) | |
| return value.hexdigest() | |
| def validate(manifest): | |
| if manifest['repo'] != REPO or not re.fullmatch('[a-f0-9]{40}', manifest['revision']): | |
| raise ValueError('Runtime assets require the configured repository and an immutable commit SHA') | |
| seen = set() | |
| for entry in manifest['files']: | |
| path = PurePosixPath(entry['path']) | |
| if path.is_absolute() or '..' in path.parts or '\\' in entry['path'] or str(path) in seen: | |
| raise ValueError('Invalid or duplicate runtime asset path') | |
| # Only the search index is a build-time asset. Original policy PDFs are fetched | |
| # on demand from their public URLs (see policy_pdfs.py) and are not stored in Git. | |
| if not entry['path'].startswith('vector_store/data_index/'): | |
| raise ValueError('Unexpected runtime asset path') | |
| if not re.fullmatch('[a-f0-9]{64}', entry['sha256']) or not 0 < entry['size'] <= 500*1024*1024: | |
| raise ValueError('Invalid runtime asset hash or size') | |
| seen.add(str(path)) | |
| def fetch(entry, revision, output): | |
| target = Path(output) / entry['path'] | |
| if target.is_file() and target.stat().st_size == entry['size'] and digest(target) == entry['sha256']: | |
| return | |
| target.parent.mkdir(parents=True, exist_ok=True) | |
| url = f'https://media.githubusercontent.com/media/{REPO}/{revision}/{entry["path"]}' | |
| for attempt in range(3): | |
| temporary = None | |
| try: | |
| checksum = hashlib.sha256() | |
| size = 0 | |
| request = urllib.request.Request(url, headers={'User-Agent': 'EPFO-Workspace-Build', 'Accept-Encoding': 'identity'}) | |
| with urllib.request.urlopen(request, timeout=90) as response, tempfile.NamedTemporaryFile(dir=target.parent, delete=False) as out: | |
| temporary = Path(out.name) | |
| for block in iter(lambda: response.read(1024*1024), b''): | |
| size += len(block) | |
| if size > entry['size']: | |
| raise ValueError('Asset exceeds its recorded size') | |
| checksum.update(block) | |
| out.write(block) | |
| if size != entry['size'] or checksum.hexdigest() != entry['sha256']: | |
| raise ValueError('Runtime asset size or SHA-256 mismatch') | |
| temporary.chmod(0o644) | |
| temporary.replace(target) | |
| print('Verified ' + entry['path'], flush=True) | |
| return | |
| except Exception: | |
| if temporary and temporary.exists(): | |
| temporary.unlink() | |
| if attempt == 2: | |
| raise | |
| time.sleep(2 * (attempt+1)) | |
| def record(revision, root=Path('.'), path=MANIFEST, preserve_revision=False): | |
| files = sorted(root/'vector_store/data_index'/name for name in | |
| ('faiss_index.index', 'faiss_index.passages.db', 'faiss_index.bm25.json.gz')) | |
| manifest = dict(repo=REPO, revision=revision, files=[dict(path=p.relative_to(root).as_posix(), size=p.stat().st_size, sha256=digest(p)) for p in files]) | |
| if preserve_revision and path.exists(): | |
| previous = json.loads(path.read_text()) | |
| validate(previous) | |
| if previous['files'] == manifest['files']: | |
| manifest['revision'] = previous['revision'] | |
| validate(manifest) | |
| path.write_text(json.dumps(manifest, indent=2)+'\n') | |
| return manifest | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('--manifest', type=Path, default=MANIFEST) | |
| parser.add_argument('--output', type=Path, default=Path('.')) | |
| mode = parser.add_mutually_exclusive_group() | |
| mode.add_argument('--record-commit') | |
| mode.add_argument('--refresh-commit', help='CI: keep the pinned revision when asset bytes have not changed') | |
| args = parser.parse_args() | |
| if args.record_commit or args.refresh_commit: | |
| record(args.record_commit or args.refresh_commit, path=args.manifest, preserve_revision=bool(args.refresh_commit)) | |
| return | |
| manifest = json.loads(args.manifest.read_text()) | |
| validate(manifest) | |
| with ThreadPoolExecutor(max_workers=4) as pool: | |
| list(pool.map(lambda entry: fetch(entry, manifest['revision'], args.output), manifest['files'])) | |
| if __name__ == '__main__': | |
| main() | |