api: factory constructs CategoryFetcher for Gelbooru + Moebooru sites

client_for_type gains optional db + site_id kwargs. When both are
passed and api_type is gelbooru or moebooru, a CategoryFetcher is
constructed and assigned to client.category_fetcher. The fetcher
owns the per-tag cache, the batch tag API fast path, and the
per-post HTML scrape fallback.

Danbooru and e621 never get a fetcher — their inline JSON
categorization is already optimal.

Test Connection dialog and scripts don't pass db/site_id, so they
get fetcher-less clients with the existing search behavior.
This commit is contained in:
pax 2026-04-09 19:15:57 -05:00
parent 834deecf57
commit f5954d1387

View File

@ -118,8 +118,22 @@ def client_for_type(
base_url: str,
api_key: str | None = None,
api_user: str | None = None,
db=None,
site_id: int | None = None,
) -> BooruClient:
"""Return the appropriate client class for an API type string."""
"""Return the appropriate client class for an API type string.
When ``db`` and ``site_id`` are passed, clients that need
post-hoc tag categorization (Gelbooru-shape, Moebooru) get a
``CategoryFetcher`` attached. The fetcher handles the per-tag
cache, the batch tag API fast path (for Gelbooru proper), and
the per-post HTML scrape fallback. Danbooru and e621 categorize
inline and don't get a fetcher.
Leave ``db``/``site_id`` as None for clients outside the main
app (Test Connection dialog, scripts) category population
becomes a no-op.
"""
clients = {
"danbooru": DanbooruClient,
"gelbooru": GelbooruClient,
@ -129,4 +143,8 @@ def client_for_type(
cls = clients.get(api_type)
if cls is None:
raise ValueError(f"Unknown API type: {api_type}")
return cls(base_url, api_key=api_key, api_user=api_user)
client = cls(base_url, api_key=api_key, api_user=api_user)
if db is not None and site_id is not None and api_type in ("gelbooru", "moebooru"):
from .category_fetcher import CategoryFetcher
client.category_fetcher = CategoryFetcher(client, db, site_id)
return client