| """core/assets.py β the product-image ASSET SOURCE (wave 18, C2-ASSET). |
| |
| WHY THIS FILE EXISTS AT ALL, given the route is three functions long: the API process is |
| HOST-AGNOSTIC by contract (`ops/verify_portability.py` checks B1/B2 β the HuggingFace SDK may |
| appear ONLY in its declared homes, all of which live under `platform/`, so the FastAPI |
| service can be lifted to Hetzner/EC2 without dragging a hub client with it). The first version |
| of `routes_assets.py` imported `huggingface_hub` directly and the gate caught it. The fetch is |
| a STORE-shaped concern anyway, so it belongs beside `core/store.py`. |
| |
| Two sources, checked by the caller in order: |
| * a local master directory (`AIOS_ASSET_DIR`) β dev, and the fastest path on the owner's box; |
| * the private HF dataset `AIOS_ASSET_REPO` (default `royal-imports/product-assets`), laid out |
| by `ingest_product_assets.py`: |
| assets/products/orig/<CODE><ext> masters, archived verbatim (owner ruling R9) |
| assets/products/web/<CODE>.png ~800px derivatives (what the app displays) |
| manifest.json {"codes": {"<CODE>": {"ext": ".png"}}, ...} |
| |
| Every failure returns None/{} rather than raising: a missing image is "no picture for that SKU", |
| never a broken catalog page. |
| """ |
| import os |
| import time |
| from pathlib import Path |
|
|
| DEFAULT_REPO = 'royal-imports/product-assets' |
| _MANIFEST_TTL = 300 |
| |
| |
| |
| |
| |
| |
| |
| _MANIFEST = {'at': 0.0, 'codes': None, 'editorial': None, 'records': None} |
|
|
|
|
| def repo_id(): |
| return os.environ.get('AIOS_ASSET_REPO', '').strip() or DEFAULT_REPO |
|
|
|
|
| def _token(): |
| return os.environ.get('HF_TOKEN') or None |
|
|
|
|
| def _load(): |
| now = time.time() |
| if _MANIFEST['codes'] is not None and now - _MANIFEST['at'] < _MANIFEST_TTL: |
| return |
| try: |
| import json |
|
|
| from huggingface_hub import hf_hub_download |
| p = hf_hub_download(repo_id(), 'manifest.json', repo_type='dataset', token=_token()) |
| man = json.loads(Path(p).read_text(encoding='utf-8')) or {} |
| codes = man.get('codes') or {} |
| editorial_ = man.get('editorial') or {} |
| records_ = man.get('records') or {} |
| except Exception: |
| codes, editorial_, records_ = {}, {}, {} |
| _MANIFEST.update(at=now, codes=codes, editorial=editorial_, records=records_) |
|
|
|
|
| def manifest(): |
| """{CODE: {'ext': '.png'}} for the configured asset repo, or {} when unreachable.""" |
| _load() |
| return _MANIFEST['codes'] |
|
|
|
|
| def editorial(): |
| """{slug: {'ext': ...}} β the editorial (non-SKU) half of the manifest; {} when unreachable.""" |
| _load() |
| return _MANIFEST['editorial'] or {} |
|
|
|
|
| def fetch(code, quality='web'): |
| """A local Path to one asset, or None. The hub cache dedupes across requests, so a container |
| downloads each file at most once.""" |
| meta = manifest().get(code) |
| if not meta: |
| return None |
| rel = (f'assets/products/web/{code}.png' if quality == 'web' |
| else f"assets/products/orig/{code}{meta.get('ext', '.png')}") |
| try: |
| from huggingface_hub import hf_hub_download |
| return Path(hf_hub_download(repo_id(), rel, repo_type='dataset', token=_token())) |
| except Exception: |
| return None |
|
|
|
|
| def fetch_editorial(slug, quality='web'): |
| """A local Path to one EDITORIAL asset, or None β same shape as fetch().""" |
| meta = editorial().get(slug) |
| if not meta: |
| return None |
| rel = (f'assets/editorial/web/{slug}.png' if quality == 'web' |
| else f"assets/editorial/orig/{slug}{meta.get('ext', '.png')}") |
| try: |
| from huggingface_hub import hf_hub_download |
| return Path(hf_hub_download(repo_id(), rel, repo_type='dataset', token=_token())) |
| except Exception: |
| return None |
|
|
|
|
| def put_editorial(slug, orig_bytes, ext, web_png): |
| """Store one editorial image: the original, its web derivative and the updated manifest in |
| ONE commit (a partial upload can never leave a listed-but-unfetchable slug). Raises on |
| failure β the route turns that into an honest 503, never a silent success. |
| |
| β manifest.json is read-modify-write with no lock; two admins uploading in the same |
| second could drop one entry. Accepted for an admin-rare action; the fix (a server-side |
| merge) rides the day uploads become routine.""" |
| import json |
|
|
| from huggingface_hub import CommitOperationAdd, HfApi, hf_hub_download |
| api = HfApi(token=_token()) |
| try: |
| p = hf_hub_download(repo_id(), 'manifest.json', repo_type='dataset', token=_token(), |
| force_download=True) |
| man = json.loads(Path(p).read_text(encoding='utf-8')) or {} |
| except Exception: |
| man = {} |
| man.setdefault('editorial', {})[slug] = {'ext': ext} |
| api.create_commit( |
| repo_id(), repo_type='dataset', |
| operations=[ |
| CommitOperationAdd(f'assets/editorial/orig/{slug}{ext}', orig_bytes), |
| CommitOperationAdd(f'assets/editorial/web/{slug}.png', web_png), |
| CommitOperationAdd('manifest.json', |
| json.dumps(man, indent=1, sort_keys=True).encode('utf-8')), |
| ], |
| commit_message=f'editorial asset: {slug}') |
| reset_cache() |
| return True |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| def records(): |
| """{'<tenant>/<id>': {'ext': ...}} β the record-image half of the manifest; {} if unreachable.""" |
| _load() |
| return _MANIFEST['records'] or {} |
|
|
|
|
| def record_ref(tenant, asset_id): |
| """The manifest key for one tenant's asset. ONE composition point, so read and write cannot |
| disagree about where a picture lives.""" |
| return f"{str(tenant or '').strip().lower()}/{str(asset_id or '').strip().lower()}" |
|
|
|
|
| def fetch_record(tenant, asset_id, quality='web'): |
| """A local Path to one RECORD image, or None β same shape as fetch()/fetch_editorial(). |
| |
| β Returns None for an id this tenant does not own, and that is the cross-tenant wall: the |
| lookup key is composed from the caller's own tenant, so an id belonging to another tenant is |
| simply absent from the manifest under this address. Indistinguishable from "no such picture", |
| which is exactly what the caller is entitled to know. |
| """ |
| ref = record_ref(tenant, asset_id) |
| meta = records().get(ref) |
| if not meta: |
| return None |
| rel = (f'assets/records/{ref}/web.png' if quality == 'web' |
| else f"assets/records/{ref}/orig{meta.get('ext', '.png')}") |
| try: |
| from huggingface_hub import hf_hub_download |
| return Path(hf_hub_download(repo_id(), rel, repo_type='dataset', token=_token())) |
| except Exception: |
| return None |
|
|
|
|
| def put_record(tenant, asset_id, orig_bytes, ext, web_png): |
| """Store one record image: original, web derivative and the updated manifest in ONE commit |
| (a partial upload can never leave a listed-but-unfetchable reference). Raises on failure β |
| the route turns that into an honest 503, never a silent success. |
| |
| β Shares `put_editorial`'s read-modify-write caveat on manifest.json: two uploads landing in |
| the same second could drop one entry. Uploads through a field are rarer than a scroll but far |
| from admin-rare, so this is the first namespace where that will actually bite β booked in the |
| wave doc rather than quietly inherited. |
| """ |
| import json |
|
|
| from huggingface_hub import CommitOperationAdd, HfApi, hf_hub_download |
| ref = record_ref(tenant, asset_id) |
| api = HfApi(token=_token()) |
| try: |
| p = hf_hub_download(repo_id(), 'manifest.json', repo_type='dataset', token=_token(), |
| force_download=True) |
| man = json.loads(Path(p).read_text(encoding='utf-8')) or {} |
| except Exception: |
| man = {} |
| man.setdefault('records', {})[ref] = {'ext': ext} |
| api.create_commit( |
| repo_id(), repo_type='dataset', |
| operations=[ |
| CommitOperationAdd(f'assets/records/{ref}/orig{ext}', orig_bytes), |
| CommitOperationAdd(f'assets/records/{ref}/web.png', web_png), |
| CommitOperationAdd('manifest.json', |
| json.dumps(man, indent=1, sort_keys=True).encode('utf-8')), |
| ], |
| commit_message=f'record image: {ref}') |
| reset_cache() |
| return True |
|
|
|
|
| def reset_cache(): |
| """Drop the memoized manifest β for gates and for a post-ingest refresh.""" |
| _MANIFEST.update(at=0.0, codes=None, editorial=None, records=None) |
|
|