Return an index for the requested source, building and caching it on first access. Local paths are revalidated against the on-disk cache on every call (subject to a cooldown scaled by build time), so an entry is rebuilt once its files change.
(self, source: str, ref: str | None = None)
| 240 | self.evict(source) |
| 241 | |
| 242 | async def get(self, source: str, ref: str | None = None) -> SembleIndex: |
| 243 | """Return an index for the requested source, building and caching it on first access. |
| 244 | |
| 245 | Local paths are revalidated against the on-disk cache on every call (subject to a |
| 246 | cooldown scaled by build time), so an entry is rebuilt once its files change. |
| 247 | """ |
| 248 | cache_key = self._compute_cache_key(source, ref) |
| 249 | await self._evict_if_stale(source, cache_key) |
| 250 | |
| 251 | if cache_key not in self._tasks: |
| 252 | model_path = await self._await_model() |
| 253 | # Re-check after the await: another caller may have populated the entry. |
| 254 | if cache_key not in self._tasks: |
| 255 | if len(self._tasks) >= _CACHE_MAX_SIZE: |
| 256 | evicted_key, _ = self._tasks.popitem(last=False) |
| 257 | self._revalidate_after.pop(evicted_key, None) |
| 258 | self._tasks[cache_key] = asyncio.create_task(self._build_and_track(source, ref, model_path, cache_key)) |
| 259 | self._tasks.move_to_end(cache_key) |
| 260 | task = self._tasks[cache_key] |
| 261 | try: |
| 262 | return await asyncio.shield(task) |
| 263 | except asyncio.CancelledError: # pragma: no cover |
| 264 | if task.done(): |
| 265 | self.evict(source) |
| 266 | raise |
| 267 | except Exception: |
| 268 | # Only evict if this task hasn't already been replaced by evict()+get(). |
| 269 | if self._tasks.get(cache_key) is task: |
| 270 | self.evict(source) |
| 271 | raise |