loopable / platform /core /assets.py
fsanyoto's picture
Deploy AIOS web (React glide grid + FastAPI slice)
ea7b176 verified
Raw
History Blame Contribute Delete
9.78 kB
"""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
#: (fetched_at, {"CODE": {...}}, {"slug": {...}}) β€” one manifest read per process per TTL.
#: DEBT-5 (2026-08-04): the same manifest.json now carries a second, NON-SKU half under
#: "editorial" β€” full-bleed lifestyle imagery for catalog gallery pages, keyed by slug.
#: ⭐ WAVE 19 R7 / C5: and a THIRD, under "records" β€” pictures uploaded through an `image` FIELD,
#: namespaced BY TENANT (`<tenant>/<id>`). The other two namespaces are Royal's catalogue assets
#: and are tenant-#0 material by nature; this one is written by any signed-in user of any tenant,
#: so the tenant is part of the address and comes from the SESSION on both write and read.
_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
# ───────────────────────────────────────── record images (wave 19, R7 / contract C5)
#
# A picture attached to ONE ROW through an `image` field, by any signed-in user. Two things make
# this namespace different from the two above, and both are about who may write it:
#
# * it is TENANT-ADDRESSED (`<tenant>/<id>`), because a nurilab user uploading a photo must not
# land anywhere a royal-imports read can reach. The tenant comes from the SESSION at both
# ends β€” never from the reference string, which is why a `rec:` ref copied between tenants
# resolves to a 404 rather than to somebody else's photograph.
# * the id is SERVER-MINTED hex, so the client never names a path component.
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)