An image query can help an operator find an asset whose filename is unknown. This project indexes the actual bytes of a small, approved image library, then accepts another image through a local API. The operator controls which library records a request may search, checks the returned candidates against the real files, and keeps a record of each corpus revision. This is a single trusted operator pilot on localhost. A shared company service would need separate identity, authorization and network review.
The big change
- What changed: The query is an image, and the result is a ranked list of approved library records.
- Why it matters: The operator can test visual discovery while keeping scope and corpus changes explicit.
- What to watch: Offline contract tests cannot establish relevance, account access or the eventual bill for your images.
What you need and what success means
Use a Windows machine with Python 3.12, an approved collection of at most 50 owned PNG or JPEG images, and permission to send both library and query images to the embedding provider. Obtain the owner’s approval for that data flow; remove sensitive EXIF, customer identifiers and confidential content before using the files. The accepted image ceiling in this project is 5 MiB and 20 million pixels per file. Keep the library, manifest, database and token accessible only to the operator. The server returns record metadata, not library image bytes.
Before making a paid request, write down real query images, expected relevant IDs, wrong but visually similar items, tags, permission scopes and a target top-five hit rate for each scope. Success requires an active index of approved files, permitted candidates from actual image requests, no unauthorized or removed IDs, the intended revision after edits, and your predeclared relevance target. The code has been checked with synthetic images and a fake embedding adapter; no business image or real provider response was tested for this article.
Put the complete project on disk
Create an empty working folder for the project. Save every file below under its exact name, all at the project root. Put approved image files in a separate images subfolder, and make a private manifest.json by replacing every value in manifest.template.json with real values. The template is an example record, not a request to download editorial data. Each file is a direct filename in images; give each record a stable ID, descriptive title, approved tags, an exact permissionRef, and a positive revision. Increase the revision when bytes or metadata change. The project tree is:
image-search-pilot/
app.py
cli.py
search_core.py
manifest.template.json
requirements.txt
test_app.py
test_search_core.py
manifest.json # your private inventory
images/ # your approved image files
private-search.sqlite # created locally at indexingThe following are the full files. Copy the contents of each code block into the matching file; keep names and indentation unchanged.
search_core.py
"""Small, single-operator image search pilot. Only Embedder calls a provider."""
from __future__ import annotations
from datetime import datetime, timezone
from hashlib import sha256
from io import BytesIO
import hmac
import json
import math
import os
from pathlib import Path
import re
import sqlite3
import warnings
from PIL import Image
MODEL = "gemini-embedding-2"
DIM = 768
MAX_IMAGE = 5 * 1024 * 1024
MAX_ITEMS = 50
MAX_CALLS_PER_UTC_DAY = 100
ID = re.compile(r"[a-z0-9][a-z0-9_-]{0,63}\Z")
def now():
return datetime.now(timezone.utc).isoformat()
def image_type(data: bytes) -> str:
if not data or len(data) > MAX_IMAGE:
raise ValueError("image size")
mime = "image/png" if data.startswith(b"\x89PNG\r\n\x1a\n") else (
"image/jpeg" if data.startswith(b"\xff\xd8\xff") and data.endswith(b"\xff\xd9") else None)
if not mime:
raise ValueError("image type")
Image.MAX_IMAGE_PIXELS = 20_000_000
try:
with warnings.catch_warnings():
warnings.simplefilter("error", Image.DecompressionBombWarning)
with Image.open(BytesIO(data), formats=["PNG", "JPEG"]) as image:
if image.format != mime[6:].upper() or image.width * image.height > 20_000_000:
raise ValueError("format/pixels")
image.load()
except (OSError, Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
raise ValueError("decode") from exc
return mime
def unit_vector(values):
if len(values) != DIM or not all(isinstance(x, (int, float)) and math.isfinite(x) for x in values):
raise ValueError("embedding dimension or finite-value check failed")
length = math.sqrt(sum(x * x for x in values))
if length == 0:
raise ValueError("zero embedding")
return [float(x) / length for x in values]
def load_manifest(path: Path, library: Path):
rows = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(rows, list) or not 1 <= len(rows) <= MAX_ITEMS:
raise ValueError("manifest needs 1 to 50 images")
library = library.resolve(strict=True)
found = []
seen = set()
for row in rows:
if not isinstance(row, dict) or set(row) != {"id", "file", "title", "tags", "permissionRef", "revision"}:
raise ValueError("manifest row fields invalid")
ident, filename = row["id"], row["file"]
if not isinstance(ident, str) or not ID.fullmatch(ident) or ident in seen:
raise ValueError("invalid or duplicate id")
seen.add(ident)
if not isinstance(filename, str) or not filename or Path(filename).name != filename:
raise ValueError("file must be a direct filename")
filepath = library / filename
if filepath.is_symlink() or not filepath.is_file() or filepath.resolve().parent != library:
raise ValueError("image must be a regular file directly in library")
title = row["title"]
tags = row["tags"]
if not isinstance(title, str) or not 1 <= len(title) <= 120:
raise ValueError("title invalid")
if not isinstance(tags, list) or len(tags) > 10 or len(set(map(str, tags))) != len(tags) or any(not isinstance(t, str) or not ID.fullmatch(t) for t in tags):
raise ValueError("tags invalid")
if not isinstance(row["permissionRef"], str) or not 1 <= len(row["permissionRef"]) <= 120:
raise ValueError("permissionRef required")
if not isinstance(row["revision"], int) or row["revision"] < 1:
raise ValueError("revision must be positive integer")
data = filepath.read_bytes()
mime = image_type(data)
found.append({**row, "mime": mime, "bytes": data, "sha256": sha256(data).hexdigest()})
return found
class SearchStore:
def __init__(self, db_path: Path, embedder, *, daily_cap=MAX_CALLS_PER_UTC_DAY):
self.db = sqlite3.connect(db_path, timeout=30)
self.db.row_factory = sqlite3.Row
self.embedder = embedder
self.daily_cap = daily_cap
self.db.executescript("""
CREATE TABLE IF NOT EXISTS assets (
id TEXT PRIMARY KEY, file TEXT NOT NULL, title TEXT NOT NULL,
tags TEXT NOT NULL, permission_ref TEXT NOT NULL, revision INTEGER NOT NULL,
sha256 TEXT NOT NULL, mime TEXT NOT NULL, model TEXT NOT NULL,
dimension INTEGER NOT NULL, vector TEXT NOT NULL, active INTEGER NOT NULL);
CREATE TABLE IF NOT EXISTS operations (
op_id TEXT PRIMARY KEY, kind TEXT NOT NULL, binding TEXT NOT NULL,
state TEXT NOT NULL CHECK(state IN ('started','ready','done','abandoned')),
vector TEXT, result TEXT, started_at TEXT NOT NULL, run_id TEXT);
CREATE TABLE IF NOT EXISTS runs (
run_id TEXT PRIMARY KEY, binding TEXT NOT NULL, base_fingerprint TEXT NOT NULL,
state TEXT NOT NULL CHECK(state IN ('preparing','done','aborted')),
started_at TEXT NOT NULL, activated_fingerprint TEXT);
""")
def close(self):
self.db.close()
def _begin(self, op_id, kind, binding, run_id=None, expected_fingerprint=None):
# Reserve atomically even if a CLI and the local API touch the same DB.
self.db.execute("BEGIN IMMEDIATE")
try:
if kind == "query" and self._pending_run():
raise RuntimeError("index run incomplete; query is paused")
# A completed replay is read under this lock only after validating its corpus.
if kind == "query" and self.corpus_fingerprint() != expected_fingerprint:
raise RuntimeError("corpus changed before query; use a new request id")
if kind == "index" and not self.db.execute(
"SELECT 1 FROM runs WHERE run_id=? AND state='preparing'", (run_id,)).fetchone():
raise RuntimeError("index run is no longer active")
existing = self.db.execute("SELECT * FROM operations WHERE op_id=?", (op_id,)).fetchone()
if existing:
if existing["kind"] != kind or existing["binding"] != binding or existing["run_id"] != run_id:
raise ValueError("operation id reused with different input or corpus")
if existing["state"] in ("started", "abandoned"):
raise RuntimeError("uncertain call: reconcile provider usage; abandon explicitly before a new id")
self.db.commit()
return existing
if self.db.execute("SELECT 1 FROM operations WHERE state='started' LIMIT 1").fetchone():
raise RuntimeError("another provider call is uncertain; reconcile it first")
day = now()[:10]
used = self.db.execute("SELECT count(*) FROM operations WHERE substr(started_at,1,10)=?", (day,)).fetchone()[0]
if used >= self.daily_cap:
raise RuntimeError("daily provider-call ceiling reached")
self.db.execute("INSERT INTO operations VALUES (?,?,?,?,?,?,?,?)", (op_id, kind, binding, "started", None, None, now(), run_id))
self.db.commit()
return None
except Exception:
self.db.rollback()
raise
def abandon(self, op_id):
row = self.db.execute("SELECT state FROM operations WHERE op_id=?", (op_id,)).fetchone()
if not row or row["state"] != "started":
raise ValueError("only a started call can be abandoned")
with self.db:
self.db.execute("UPDATE operations SET state='abandoned' WHERE op_id=?", (op_id,))
def abort_run(self, run_id):
self.db.execute("BEGIN IMMEDIATE")
try:
run = self.db.execute("SELECT state FROM runs WHERE run_id=?", (run_id,)).fetchone()
if not run or run["state"] != "preparing":
raise ValueError("only an incomplete run can be aborted")
if self.db.execute("SELECT 1 FROM operations WHERE run_id=? AND state='started'", (run_id,)).fetchone():
raise RuntimeError("reconcile and abandon uncertain provider calls before aborting run")
self.db.execute("UPDATE operations SET state='abandoned' WHERE run_id=? AND state='ready'", (run_id,))
self.db.execute("UPDATE runs SET state='aborted' WHERE run_id=?", (run_id,))
self.db.commit()
except Exception:
self.db.rollback()
raise
def _pending_run(self):
return self.db.execute("SELECT run_id FROM runs WHERE state='preparing' LIMIT 1").fetchone()
def _vector(self, op_id, existing, data, mime):
if existing:
return json.loads(existing["vector"])
# The started receipt is committed before this potentially chargeable call.
vector = unit_vector(self.embedder.embed(data, mime))
with self.db:
self.db.execute("UPDATE operations SET state='ready',vector=? WHERE op_id=?", (json.dumps(vector), op_id))
return vector
def _prevalidate(self, rows):
seen = set()
if not 1 <= len(rows) <= MAX_ITEMS:
raise ValueError("manifest needs 1 to 50 images")
for row in rows:
if row["id"] in seen:
raise ValueError("duplicate id")
seen.add(row["id"])
old = self.db.execute("SELECT * FROM assets WHERE id=?", (row["id"],)).fetchone()
if old and row["revision"] < old["revision"]:
raise ValueError("revision went backwards")
if old and row["revision"] == old["revision"] and (
old["sha256"] != row["sha256"] or old["file"] != row["file"] or
old["title"] != row["title"] or json.loads(old["tags"]) != row["tags"] or
old["permission_ref"] != row["permissionRef"]):
raise ValueError("changed bytes or metadata require a new revision")
def _run_binding(self, rows):
fields = ("id", "file", "title", "tags", "permissionRef", "revision", "sha256", "mime")
return json.dumps([[r[f] for f in fields] for r in sorted(rows, key=lambda r: r["id"])] + [MODEL, DIM], sort_keys=True)
def _start_run(self, rows, run_id, binding):
self.db.execute("BEGIN IMMEDIATE")
try:
prior = self.db.execute("SELECT * FROM runs WHERE run_id=?", (run_id,)).fetchone()
if prior:
if prior["binding"] != binding or prior["state"] == "aborted":
raise ValueError("run id reused with changed manifest or aborted run")
if prior["state"] == "preparing" and prior["base_fingerprint"] != self.corpus_fingerprint():
raise RuntimeError("base corpus changed during incomplete run")
if prior["state"] == "done" and prior["activated_fingerprint"] != self.corpus_fingerprint():
raise ValueError("completed run does not match current corpus; choose a new run id")
self.db.commit()
return prior["state"] == "done"
if self._pending_run():
raise RuntimeError("another index run is incomplete; resume or abort it")
self._prevalidate(rows) # Reject the whole manifest before any row mutation.
self.db.execute("INSERT INTO runs VALUES (?,?,?,?,?,?)",
(run_id, binding, self.corpus_fingerprint(), "preparing", now(), None))
self.db.commit()
return False
except Exception:
self.db.rollback()
raise
def index(self, rows, run_id):
if not ID.fullmatch(run_id):
raise ValueError("run id invalid")
binding = self._run_binding(rows)
if self._start_run(rows, run_id, binding):
return {"active": len(rows), "model": MODEL, "dimension": DIM, "runState": "done"}
ids = {r["id"] for r in rows}
vectors = {}
for row in rows:
old = self.db.execute("SELECT * FROM assets WHERE id=?", (row["id"],)).fetchone()
same_image = old and old["sha256"] == row["sha256"] and old["model"] == MODEL and old["dimension"] == DIM
if same_image:
vector = json.loads(old["vector"])
else:
op_id = f"{run_id}-{row['id']}-{row['revision']}"
binding = json.dumps([row["id"], row["revision"], row["sha256"], MODEL, DIM])
prior = self._begin(op_id, "index", binding, run_id)
vector = self._vector(op_id, prior, row["bytes"], row["mime"])
vectors[row["id"]] = vector
# All public corpus changes, including removals and run completion, commit together.
self.db.execute("BEGIN IMMEDIATE")
try:
run = self.db.execute("SELECT * FROM runs WHERE run_id=?", (run_id,)).fetchone()
if not run or run["state"] != "preparing" or run["base_fingerprint"] != self.corpus_fingerprint():
raise RuntimeError("base corpus changed before activation")
self._prevalidate(rows)
for row in rows:
self.db.execute("""INSERT INTO assets VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(id) DO UPDATE SET file=excluded.file,title=excluded.title,tags=excluded.tags,
permission_ref=excluded.permission_ref,revision=excluded.revision,sha256=excluded.sha256,
mime=excluded.mime,model=excluded.model,dimension=excluded.dimension,
vector=excluded.vector,active=1""",
(row["id"], row["file"], row["title"], json.dumps(row["tags"]), row["permissionRef"], row["revision"], row["sha256"], row["mime"], MODEL, DIM, json.dumps(vectors[row["id"]]), 1))
for asset in self.db.execute("SELECT id FROM assets WHERE active=1").fetchall():
if asset["id"] not in ids:
self.db.execute("UPDATE assets SET active=0 WHERE id=?", (asset["id"],))
self.db.execute("UPDATE operations SET state='done' WHERE run_id=? AND state='ready'", (run_id,))
self.db.execute("UPDATE runs SET state='done',activated_fingerprint=? WHERE run_id=?",
(self.corpus_fingerprint(), run_id))
self.db.commit()
except Exception:
self.db.rollback()
raise
return {"active": len(ids), "model": MODEL, "dimension": DIM, "runState": "done"}
def corpus_fingerprint(self):
rows = self.db.execute("SELECT id,file,title,sha256,revision,tags,permission_ref,model,dimension FROM assets WHERE active=1 ORDER BY id").fetchall()
return sha256(json.dumps([tuple(r) for r in rows]).encode()).hexdigest()
def search(self, data, *, request_id, token, expected_token, allowed_permissions, tag=None, limit=5):
if not expected_token or not hmac.compare_digest(token or "", expected_token):
raise PermissionError("invalid bearer token")
mime = image_type(data)
if not ID.fullmatch(request_id) or not isinstance(limit, int) or not 1 <= limit <= 10:
raise ValueError("request id or limit invalid")
if tag is not None and not ID.fullmatch(tag):
raise ValueError("tag invalid")
if not allowed_permissions or any(not isinstance(p, str) for p in allowed_permissions):
raise ValueError("permission scope missing")
scope = sorted(set(allowed_permissions))
if self._pending_run():
raise RuntimeError("index run incomplete; query is paused")
fingerprint = self.corpus_fingerprint()
binding = json.dumps([sha256(data).hexdigest(), mime, fingerprint, scope, tag, limit, MODEL, DIM])
prior = self._begin(request_id, "query", binding, expected_fingerprint=fingerprint)
if prior and prior["state"] == "done":
return json.loads(prior["result"])
vector = self._vector(request_id, prior, data, mime)
self.db.execute("BEGIN IMMEDIATE")
try:
if self._pending_run() or self.corpus_fingerprint() != fingerprint:
self.db.execute("UPDATE operations SET state='abandoned',result=? WHERE op_id=?",
(json.dumps({"reason": "corpus changed or index incomplete after embedding"}), request_id))
self.db.commit()
raise RuntimeError("corpus changed during query; use a new request id")
ranked = []
for row in self.db.execute("SELECT * FROM assets WHERE active=1"):
if row["permission_ref"] not in scope or (tag is not None and tag not in json.loads(row["tags"])):
continue
if row["model"] != MODEL or row["dimension"] != DIM:
raise RuntimeError("mixed embedding spaces")
score = sum(a*b for a,b in zip(vector, json.loads(row["vector"])))
ranked.append({"id": row["id"], "title": row["title"], "tags": json.loads(row["tags"]), "revision": row["revision"], "score": round(score, 6)})
ranked.sort(key=lambda r: (-r["score"], r["id"]))
result = {"requestId": request_id, "corpusFingerprint": fingerprint, "results": ranked[:limit]}
self.db.execute("UPDATE operations SET state='done',result=? WHERE op_id=?", (json.dumps(result), request_id))
self.db.commit()
return result
except Exception:
self.db.rollback()
raise
def status(self):
return {"active": self.db.execute("SELECT count(*) FROM assets WHERE active=1").fetchone()[0],
"corpusFingerprint": self.corpus_fingerprint(),
"pendingRuns": [dict(r) for r in self.db.execute("SELECT run_id,state,started_at FROM runs WHERE state='preparing'")],
"operations": [dict(r) for r in self.db.execute("SELECT op_id,kind,state,started_at,run_id FROM operations ORDER BY started_at")]}
class GeminiEmbedder:
def __init__(self, api_key):
from google.genai import Client, types
self.client = Client(api_key=api_key, vertexai=False, enterprise=False,
http_options=types.HttpOptions(timeout=30000, retry_options=types.HttpRetryOptions(attempts=1)))
def embed(self, data, mime):
from google.genai import types
response = self.client.models.embed_content(model=MODEL,
contents=[types.Part.from_bytes(data=data, mime_type=mime)],
config=types.EmbedContentConfig(output_dimensionality=DIM))
if len(response.embeddings or []) != 1:
raise ValueError("embedding count")
return response.embeddings[0].values
def close(self):
self.client.close()cli.py
"""Operator CLI. Index is chargeable; check and status are offline."""
import argparse
import json
import os
from pathlib import Path
from search_core import GeminiEmbedder, SearchStore, load_manifest
def main():
parser = argparse.ArgumentParser()
parser.add_argument("command", choices=("check", "index", "status", "abandon", "abort-run"))
parser.add_argument("--manifest", type=Path)
parser.add_argument("--library", type=Path)
parser.add_argument("--db", type=Path)
parser.add_argument("--run-id")
parser.add_argument("--op-id")
args = parser.parse_args()
if args.command in ("check", "index"):
if not args.manifest or not args.library:
parser.error("check/index require --manifest and --library")
rows = load_manifest(args.manifest, args.library)
print(json.dumps({"validatedRows": len(rows), "ids": [r["id"] for r in rows]}))
if args.command == "check":
return
if not args.db:
parser.error("index/status/abandon require --db")
if args.command == "index" and not args.run_id:
parser.error("index requires --run-id")
if args.command == "abandon" and not args.op_id:
parser.error("abandon requires --op-id")
if args.command == "abort-run" and not args.run_id:
parser.error("abort-run requires --run-id")
embedder = GeminiEmbedder(os.environ["GEMINI_API_KEY"]) if args.command == "index" else None
store = SearchStore(args.db, embedder)
try:
if args.command == "index":
print(json.dumps(store.index(rows, args.run_id)))
elif args.command == "status":
print(json.dumps(store.status()))
elif args.command == "abort-run":
store.abort_run(args.run_id)
print(json.dumps({"abortedRun": args.run_id}))
else:
store.abandon(args.op_id)
print(json.dumps({"abandoned": args.op_id}))
finally:
store.close()
if embedder:
embedder.close()
if __name__ == "__main__":
main()app.py
"""Local-only API. Start one worker and bind 127.0.0.1."""
import os
import hmac
from pathlib import Path
import fastapi
from search_core import GeminiEmbedder, MAX_IMAGE, SearchStore
app = fastapi.FastAPI(openapi_url=None)
@app.post("/search")
async def search(request: fastapi.Request, request_id: str, tag: str | None = None, limit: int = 5):
token = request.headers.get("authorization", "")
expected = os.environ.get("SEARCH_API_TOKEN")
if not expected or not hmac.compare_digest(token, f"Bearer {expected}"):
raise fastapi.HTTPException(503 if not expected else 401)
if request.headers.get("content-type") not in ("image/png", "image/jpeg"):
raise fastapi.HTTPException(415)
body = bytearray()
async for chunk in request.stream():
if len(body) + len(chunk) > MAX_IMAGE:
raise fastapi.HTTPException(413)
body += chunk
embedder = None
store = None
try:
key = os.environ["GEMINI_API_KEY"]
db = Path(os.environ["SEARCH_DB"])
scopes = [v for v in os.environ["SEARCH_ALLOWED_PERMISSION_REFS"].split(",") if v]
embedder = GeminiEmbedder(key)
store = SearchStore(db, embedder)
return store.search(bytes(body), request_id=request_id,
token=token[7:], expected_token=expected,
allowed_permissions=scopes, tag=tag, limit=limit)
except (PermissionError, ValueError, RuntimeError, KeyError) as exc:
raise fastapi.HTTPException({PermissionError: 401, ValueError: 400}.get(type(exc), 409))
finally:
if store:
store.close()
if embedder:
embedder.close()manifest.template.json
[
{
"id": "your-owned-image-id",
"file": "your-owned-image.png",
"title": "Your descriptive title",
"tags": ["your-approved-tag"],
"permissionRef": "your-internal-access-scope",
"revision": 1
}
]requirements.txt
google-genai==2.33.0
fastapi==0.162.0
uvicorn==0.54.0
pillow==13.0.0test_search_core.py
import json
import os
from base64 import b64decode
from pathlib import Path
import tempfile
import unittest
from types import SimpleNamespace
from unittest.mock import patch
from search_core import DIM, GeminiEmbedder, SearchStore, image_type, load_manifest
PNG_A = b64decode("iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVR4nGP8zwAFTDAGEgsAHVsBBeg3qMUAAAAASUVORK5CYII=")
PNG_B = b64decode("iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAE0lEQVR4nGNkYPjPAAZMEAqFBQAbXQEF2cdRlAAAAABJRU5ErkJggg==")
class FakeEmbedder:
def __init__(self):
self.calls = []
def embed(self, data, mime):
self.calls.append(data)
vector = [0.0] * DIM
vector[0 if data == PNG_A else 1] = 1.0
return vector
class SearchCoreTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.lib = self.root / "images"
self.lib.mkdir()
(self.lib / "a.png").write_bytes(PNG_A)
(self.lib / "b.png").write_bytes(PNG_B)
self.manifest = self.root / "manifest.json"
self.rows = [
{"id": "a", "file": "a.png", "title": "A", "tags": ["red"], "permissionRef": "team-a", "revision": 1},
{"id": "b", "file": "b.png", "title": "B", "tags": ["blue"], "permissionRef": "team-b", "revision": 1},
]
self.manifest.write_text(json.dumps(self.rows))
self.fake = FakeEmbedder()
self.store = SearchStore(self.root / "search.sqlite", self.fake)
self.addCleanup(self.store.close)
def load(self):
return load_manifest(self.manifest, self.lib)
def query(self, **kw):
return self.store.search(PNG_A, request_id="q1", token="secret",
expected_token="secret", allowed_permissions=["team-a"], **kw)
def test_image_validation_and_manifest_path(self):
self.assertEqual(image_type(PNG_A), "image/png")
with self.assertRaises(ValueError):
image_type(b"not an image")
self.rows[0]["file"] = "../a.png"
self.manifest.write_text(json.dumps(self.rows))
with self.assertRaises(ValueError):
self.load()
def test_index_filter_update_and_removal(self):
self.store.index(self.load(), "run1")
self.assertEqual(len(self.fake.calls), 2)
result = self.query(tag="red")
self.assertEqual([r["id"] for r in result["results"]], ["a"])
self.assertEqual(len(self.fake.calls), 3)
self.assertEqual(self.query(tag="red"), result)
self.assertEqual(len(self.fake.calls), 3)
with self.assertRaises(ValueError):
self.query(tag="blue") # Same request id, changed binding.
self.rows[0]["title"] = "Updated A"
self.rows[0]["revision"] = 2
self.rows.pop()
self.manifest.write_text(json.dumps(self.rows))
self.store.index(self.load(), "run2")
self.assertEqual(len(self.fake.calls), 3) # Metadata-only edit saves a call.
self.assertEqual(self.store.status()["active"], 1)
with self.assertRaises(ValueError):
self.query(tag="red") # Corpus changed under request id.
def test_auth_before_embedding_and_permission_filter(self):
self.store.index(self.load(), "run1")
with self.assertRaises(PermissionError):
self.store.search(PNG_A, request_id="bad", token="wrong",
expected_token="secret", allowed_permissions=["team-a"])
self.assertEqual(len(self.fake.calls), 2)
result = self.store.search(PNG_A, request_id="q2", token="secret",
expected_token="secret", allowed_permissions=["team-b"])
self.assertEqual([r["id"] for r in result["results"]], ["b"])
def test_uncertain_call_never_auto_retries(self):
self.fake.embed = lambda data, mime: (_ for _ in ()).throw(TimeoutError("unknown provider outcome"))
with self.assertRaises(TimeoutError):
self.store.index(self.load(), "run1")
self.assertEqual(self.store.status()["operations"][0]["state"], "started")
with self.assertRaises(RuntimeError):
self.store.index(self.load(), "run1")
self.store.abandon("run1-a-1")
with self.assertRaises(RuntimeError):
self.store.index(self.load(), "run1")
def test_daily_call_ceiling(self):
limited = SearchStore(self.root / "limited.sqlite", FakeEmbedder(), daily_cap=1)
self.addCleanup(limited.close)
with self.assertRaises(RuntimeError):
limited.index(self.load(), "run1")
self.assertEqual(len(limited.status()["operations"]), 1)
def test_google_sdk_contract_and_route_without_transport(self):
with patch.dict(os.environ, {"GOOGLE_GENAI_USE_VERTEXAI": "true", "GOOGLE_GENAI_USE_ENTERPRISE": "true"}):
adapter = GeminiEmbedder("offline-key")
self.assertEqual(adapter.client._api_client._http_options.base_url, "https://generativelanguage.googleapis.com/")
with patch.object(adapter.client.models, "embed_content", return_value=SimpleNamespace(embeddings=[SimpleNamespace(values=[1.0] * DIM)])) as call:
adapter.embed(PNG_A, "image/png")
args = call.call_args.kwargs
self.assertEqual(args["contents"][0].inline_data.mime_type, "image/png")
self.assertEqual(args["config"].output_dimensionality, DIM)
adapter.close()
def test_query_rejects_concurrent_metadata_permission_update(self):
self.store.index(self.load(), "seed")
original = self.store.corpus_fingerprint()
def update_during_call(data, mime):
writer = SearchStore(self.root / "search.sqlite", FakeEmbedder())
try:
changed = self.load()
changed[0]["title"] = "New A"
changed[0]["permissionRef"] = "team-b"
changed[0]["revision"] = 2
writer.index(changed, "metadata")
finally:
writer.close()
return [1.0] + [0.0] * (DIM - 1)
self.fake.embed = update_during_call
with self.assertRaisesRegex(RuntimeError, "corpus changed"):
self.query()
self.assertNotEqual(self.store.corpus_fingerprint(), original)
op = self.store.db.execute("SELECT state,result FROM operations WHERE op_id='q1'").fetchone()
self.assertEqual(op["state"], "abandoned")
self.assertNotIn("results", op["result"])
self.fake.embed = FakeEmbedder().embed
result = self.store.search(PNG_A, request_id="q2", token="secret", expected_token="secret",
allowed_permissions=["team-a"])
self.assertEqual(result["results"], [])
def test_query_rejects_concurrent_removal(self):
self.store.index(self.load(), "seed")
def remove_during_call(data, mime):
writer = SearchStore(self.root / "search.sqlite", FakeEmbedder())
try:
writer.index([self.load()[1]], "remove-a")
finally:
writer.close()
return [1.0] + [0.0] * (DIM - 1)
self.fake.embed = remove_during_call
with self.assertRaisesRegex(RuntimeError, "corpus changed"):
self.query()
self.assertEqual(self.store.status()["active"], 1)
self.assertEqual(self.store.db.execute("SELECT state FROM operations WHERE op_id='q1'").fetchone()[0], "abandoned")
def _cached_replay_race(self, change):
self.store.index(self.load(), "seed")
cached = self.query(tag="red")
before = len(self.fake.calls)
original_begin = self.store._begin
def change_before_begin(*args, **kwargs):
writer = SearchStore(self.root / "search.sqlite", FakeEmbedder())
try:
rows = self.load()
writer.index(change(rows), "concurrent")
finally:
writer.close()
return original_begin(*args, **kwargs)
with patch.object(self.store, "_begin", side_effect=change_before_begin):
with self.assertRaisesRegex(RuntimeError, "corpus changed before query"):
self.query(tag="red")
self.assertEqual(len(self.fake.calls), before)
self.assertNotEqual(self.store.corpus_fingerprint(), cached["corpusFingerprint"])
def test_cached_replay_rejects_permission_revocation(self):
def change(rows):
rows[0]["permissionRef"] = "team-b"
rows[0]["revision"] = 2
return rows
self._cached_replay_race(change)
self.assertEqual(self.store.search(PNG_A, request_id="q2", token="secret",
expected_token="secret", allowed_permissions=["team-a"])["results"], [])
def test_cached_replay_rejects_removal(self):
self._cached_replay_race(lambda rows: rows[1:])
self.assertEqual(self.store.status()["active"], 1)
def test_cached_replay_rejects_tag_change(self):
def change(rows):
rows[0]["tags"] = ["blue"]
rows[0]["revision"] = 2
return rows
self._cached_replay_race(change)
def test_cached_replay_rejects_revision_change(self):
def change(rows):
rows[0]["title"] = "Revised A"
rows[0]["revision"] = 2
return rows
self._cached_replay_race(change)
def test_unchanged_cached_replay_skips_embedding(self):
self.store.index(self.load(), "seed")
cached = self.query(tag="red")
before = len(self.fake.calls)
self.assertEqual(self.query(tag="red"), cached)
self.assertEqual(len(self.fake.calls), before)
def test_prevalidates_entire_manifest_before_metadata_changes(self):
self.rows[1]["revision"] = 2
self.manifest.write_text(json.dumps(self.rows))
self.store.index(self.load(), "seed")
changed = self.load()
changed[0]["revision"] = 2
changed[0]["title"] = "Partial A"
changed[1]["revision"] = 1
with self.assertRaisesRegex(ValueError, "revision went backwards"):
self.store.index(changed, "invalid")
self.assertEqual(self.store.db.execute("SELECT title FROM assets WHERE id='a'").fetchone()[0], "A")
self.assertEqual(self.store.status()["pendingRuns"], [])
def test_interrupted_run_keeps_old_corpus_and_can_abort(self):
self.store.index(self.load(), "seed")
old_fingerprint = self.store.corpus_fingerprint()
changed = self.load()[:1]
changed[0]["revision"] = 2
changed[0]["bytes"] = PNG_B
changed[0]["sha256"] = __import__("hashlib").sha256(PNG_B).hexdigest()
self.fake.embed = lambda data, mime: (_ for _ in ()).throw(TimeoutError("uncertain"))
with self.assertRaises(TimeoutError):
self.store.index(changed, "replace-and-remove")
self.assertEqual(self.store.corpus_fingerprint(), old_fingerprint)
self.assertEqual(self.store.status()["pendingRuns"][0]["run_id"], "replace-and-remove")
with self.assertRaisesRegex(RuntimeError, "index run incomplete"):
self.query()
with self.assertRaisesRegex(RuntimeError, "reconcile"):
self.store.abort_run("replace-and-remove")
self.store.abandon("replace-and-remove-a-2")
self.store.abort_run("replace-and-remove")
self.assertEqual(self.store.status()["pendingRuns"], [])
self.assertEqual(self.store.corpus_fingerprint(), old_fingerprint)
self.fake.embed = FakeEmbedder().embed
self.store.index(changed, "new-run")
self.assertEqual(self.store.status()["active"], 1)
def test_later_row_failure_keeps_metadata_and_removals_unapplied(self):
self.store.index(self.load(), "seed")
changed = self.load()
changed[0]["title"] = "Not yet visible"
changed[0]["revision"] = 2
changed[1]["revision"] = 2
changed[1]["bytes"] = PNG_A
changed[1]["sha256"] = __import__("hashlib").sha256(PNG_A).hexdigest()
self.fake.embed = lambda data, mime: (_ for _ in ()).throw(TimeoutError("uncertain"))
with self.assertRaises(TimeoutError):
self.store.index(changed, "late-failure")
self.assertEqual(self.store.db.execute("SELECT title FROM assets WHERE id='a'").fetchone()[0], "A")
self.assertEqual(self.store.status()["active"], 2)
self.assertEqual(self.store.status()["pendingRuns"][0]["run_id"], "late-failure")
def test_ready_vector_replay_activates_without_new_provider_call(self):
rows = self.load()[:1]
original = self.store._prevalidate
calls = 0
def interrupt_after_embedding(items):
nonlocal calls
calls += 1
if calls == 2:
raise RuntimeError("local interruption before activation")
return original(items)
with patch.object(self.store, "_prevalidate", side_effect=interrupt_after_embedding):
with self.assertRaisesRegex(RuntimeError, "before activation"):
self.store.index(rows, "ready-replay")
self.assertEqual(self.store.status()["active"], 0)
self.assertEqual(self.store.status()["pendingRuns"][0]["run_id"], "ready-replay")
self.assertEqual(len(self.fake.calls), 1)
self.store.index(rows, "ready-replay")
self.assertEqual(len(self.fake.calls), 1)
self.assertEqual(self.store.status()["active"], 1)
if __name__ == "__main__":
unittest.main()test_app.py
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from fastapi.testclient import TestClient
import app as module
from search_core import SearchStore, DIM, MAX_IMAGE
from test_search_core import PNG_A as PNG
class FakeEmbedder:
def __init__(self, key):
self.calls = 0
def embed(self, data, mime):
self.calls += 1
return [1.0] + [0.0] * (DIM - 1)
def close(self):
pass
class ApiTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.db = Path(self.tmp.name) / "db.sqlite"
fake = FakeEmbedder("fake")
store = SearchStore(self.db, fake)
store.index([{"id":"a","file":"a.png","title":"A","tags":["red"],
"permissionRef":"team-a","revision":1,"mime":"image/png",
"bytes":PNG,"sha256":__import__("hashlib").sha256(PNG).hexdigest()}], "seed")
store.close()
self.env = patch.dict(os.environ, {"SEARCH_API_TOKEN":"secret", "GEMINI_API_KEY":"fake",
"SEARCH_DB":str(self.db), "SEARCH_ALLOWED_PERMISSION_REFS":"team-a"})
self.env.start()
self.addCleanup(self.env.stop)
self.mock = patch.object(module, "GeminiEmbedder", FakeEmbedder)
self.mock.start()
self.addCleanup(self.mock.stop)
self.client = TestClient(module.app)
def post(self, rid="req1", token="Bearer secret", tag="red", data=PNG, mime="image/png", status=200):
result = self.client.post("/search", params={"request_id": rid, "tag": tag},
content=data, headers={"content-type": mime, **({"authorization": token} if token else {})})
self.assertEqual(result.status_code, status)
return result
def test_boundary_and_filter(self):
for token in (None, "Bearer wrong"):
self.post(token=token, status=401)
good = self.post().json()
self.assertEqual((good["results"][0]["id"], self.post().json()), ("a", good))
self.post(tag="blue", status=400)
def test_request_size_and_type(self):
self.post(data=b"a" * (MAX_IMAGE + 1), status=413)
self.post(data=b"bad", status=400)
self.post(mime="text/plain", status=415)
if __name__ == "__main__":
unittest.main()Check the installation and inventory
In PowerShell, change to your project folder. Replace the whole quoted folder path in both shells; quotes preserve spaces. This uses the virtual environment interpreter directly, without changing execution policy. Use that interpreter for every later Python command. Installation from your approved package registry requires ordinary package access, but check and the two test files make no provider call.
Set-Location 'C:\path\to\image-search-pilot'
py -3.12 -m venv .venv
$Python = (Resolve-Path '.\.venv\Scripts\python.exe').Path
& $Python --version
& $Python -m pip install -r .\requirements.txt
& $Python -B -m unittest -v test_search_core.py test_app.py
& $Python .\cli.py check --manifest .\manifest.json --library .\imagesIf you open another PowerShell window, run Set-Location and the $Python = ... line again. The code accepts direct child filenames and rejects escapes, symlinks, duplicate IDs, bad scopes and invalid images. Fix a failed check in the files you own before indexing. The supplied offline suite has 19 tests with synthetic fixtures and a fake adapter. A pass confirms those local paths under these pinned dependencies; it does not prove image relevance, provider access or billing behavior.
Bind secrets and index the whole library
Keep the real key and a long random local API token in your approved secret manager. Retrieve their values and enter them at these masked prompts in the PowerShell window that will index or run the server. This binds plaintext only to that process environment; do not print it, paste it into a command or save it in a file. In a new shell, repeat the relevant prompt: indexing and the server need both values; the query client needs only SEARCH_API_TOKEN. Never put the token in the URL or command history.
$env:GEMINI_API_KEY = [System.Net.NetworkCredential]::new('', (Read-Host 'Gemini API key from your secret manager' -AsSecureString)).Password
$env:SEARCH_API_TOKEN = [System.Net.NetworkCredential]::new('', (Read-Host 'Local API token from your secret manager' -AsSecureString)).PasswordBind SEARCH_DB to the private database file and SEARCH_ALLOWED_PERMISSION_REFS to the exact comma-separated scopes this one operator is allowed to search. For example, if your manifest uses an approved scope named catalog-team, the scope variable contains that exact string.
Use Developer API; clear backend flags and proxies. With the environment bound, run:
$env:SEARCH_DB = (Join-Path (Get-Location) 'private-search.sqlite')
$env:SEARCH_ALLOWED_PERMISSION_REFS = 'catalog-team'
& $Python .\cli.py index --manifest .\manifest.json --library .\images --db .\private-search.sqlite --run-id first-owned-library
& $Python .\cli.py status --db .\private-search.sqliteReplace catalog-team with all and only the scopes you have approved. A different approved set can be written as comma-separated exact values. Replace first-owned-library with a unique ID for the initial run; retain that ID and the exact manifest while resolving an interrupted run. The index validates the proposed manifest before it starts, obtains vectors for changed images, and activates titles, scopes, revisions and removals in one SQLite transaction when the entire run is ready. Queries pause while a run is incomplete. A metadata-only edit reuses an existing vector. status reports the active count, corpus fingerprint, pending runs and operation states. Do not hand-edit the database to make an incomplete run appear done.
Each provider attempt is recorded as started before the call. After a completed vector is recorded as ready, replaying the same bound run can finish locally without a second call. An uncertain started state needs a deliberate check of provider usage and local logs. If reconciliation supports abandoning that exact attempt, run abandon with the operation ID from status. An index run with an abandoned attempt cannot resume: abort that run ID, then begin a new one after correcting the cause. An interruption with only ready operations can reuse the same ID and unchanged manifest. These records prevent blind retries; they cannot reveal whether an uncertain provider call was billed.
& $Python .\cli.py status --db .\private-search.sqlite
& $Python .\cli.py abandon --db .\private-search.sqlite --op-id 'OP_ID_FROM_STATUS'
& $Python .\cli.py abort-run --db .\private-search.sqlite --run-id 'RUN_ID_FROM_STATUS'Use the last two commands only for the reconciled failure they describe. Substitute the entire quoted placeholder, including its example text, with the exact ID reported locally. Never run them as a routine part of a successful index.
Send a real image request
Start the API in a PowerShell window with the same environment binding. The token is shared by this one trusted operator, and the server binds to localhost with one worker. Keep this window open while using the client below.
& '.\.venv\Scripts\uvicorn.exe' app:app --host 127.0.0.1 --port 8765 --workers 1 --no-access-logIn a second PowerShell window, bind the secrets again through your secret manager, change to the same folder, and use this client. Set $QueryImage to an approved real image and substitute the entire scope tag with one you actually assigned in your manifest. Use a new request ID for each distinct query or after a corpus change. The API selects allowed scopes from its server environment; callers cannot widen them in the URL. This example sends raw image bytes, with no multipart envelope, and reads the JSON response.
Set-Location 'C:\path\to\image-search-pilot'
$QueryImage = (Resolve-Path '.\images\your-approved-query.png').Path
$RequestId = 'owned-query-001'
$Tag = 'your-approved-tag'
$ImageBytes = [System.IO.File]::ReadAllBytes($QueryImage)
$Uri = 'http://127.0.0.1:8765/search?request_id=' + [uri]::EscapeDataString($RequestId) + '&tag=' + [uri]::EscapeDataString($Tag) + '&limit=5'
$Response = Invoke-RestMethod -Method Post -Uri $Uri -Headers @{ Authorization = "Bearer $env:SEARCH_API_TOKEN" } -ContentType 'image/png' -Body $ImageBytes
$Response | ConvertTo-Json -Depth 6Use image/jpeg as the content type for a JPEG query and a path to a JPEG file. Do not use an image merely because its filename is present in the library: confirm you have permission to send that query image too. A response contains requestId, corpusFingerprint and ranked results with IDs, titles, tags, revisions and similarity scores. Compare each returned ID with the manifest and inspect the corresponding actual image through your approved library interface. A score orders candidates; it is not a probability that a match is correct. Record relevant IDs, misses and false matches against the target written before testing.
The request body is limited to 5 MiB. A completed request ID can return its saved result without another provider call only while the bound corpus is current. Reusing that ID with changed bytes, filters, scope or corpus fails closed. If the corpus changes during the request, wait for indexing to finish and send a new ID. Reconcile uncertain query usage before abandonment and a new ID. A ready query can finish locally from its saved vector while the corpus stays unchanged. For errors raised inside the route, use local status and your request ID to investigate the generic response: an absent server token yields 503, a wrong token 401, wrong media type 415, oversized body 413, and invalid image, ID, tag or limit values 400. A pending or changed corpus can return 409. The framework rejects a missing request_id or a noninteger limit before the route runs with 422 validation details.
Test revisions, quality and cost before any wider use
Change one approved image or its metadata, increase that record's revision, remove another record from the manifest, and use a new run ID to index the whole updated manifest. Check that the removed ID disappears from new results and that the edited record has the intended revision. Run your predeclared queries under every approved scope and relevant tag. Include similar looking wrong assets. Investigate false matches and misses against the actual images; do not report a retrieval score until the real-image evaluation is complete. A failing target may call for clearer labels, better crops or a different task, with a new recorded test.
At the 27 October 2026 check, Google's pricing page listed a paid standard image-input estimate of $0.00012 per image. Fifty first-pass index calls and fifty queries imply about $0.012 at that unit estimate, not an invoice ceiling. Check your account's current tier and actual usage. Failed or uncertain attempts, retries, changed prices and other billing terms can alter cost. This local database reserves at most 100 call starts per UTC day. Set a provider billing budget separately. The small SQLite scan and one shared token fit one trusted operator; expansion calls for a reviewed storage, identity, authorization, transport and monitoring design.
For related background, our five-media local-search guide handles typed questions and different input media. Our local multimodal prototype uses a downloadable model. This image-query pilot has a distinct request, permission and corpus-revision task.



