comicbox.online_session

source module comicbox.online_session

OnlineSession β€” Codex-facing API for batch online metadata tagging.

Wraps the existing Comicbox.run_online_lookup() per-file flow with a Codex-friendly surface:

  • Per-source credential validation up front (fail-fast).
  • Direct :class:MatchMode API (no string alias layer; ASK is rejected since the session has no built-in prompt resolver for it).
  • One programmatic PromptHandler per session that the matcher calls whenever it would otherwise hit the CLI questionary prompt.
  • Event stream via on_event= for UI feedback.
  • Cancellation token: callers stop the batch between files.

The internal matcher / selector / rate-limit machinery is untouched; this module is a faΓ§ade.

Classes

source class OnlineCredentials()

Credentials for the online sources Codex may enable.

source class OnlinePrompt()

Codex-facing handoff for an ambiguous match.

The PromptHandler receives one of these and returns a PromptResponse.

source class PromptResponse()

Codex-side decision for one prompt.

action mirrors :data:SelectorAction; payload is the index for choose, the "<source>:<id>" string for manual, the new :class:MatchMode value (e.g. "auto") for set_policy, or None otherwise.

source class PromptHandler()

Bases : Protocol

Codex’s prompt-resolution callback.

All handlers must implement :meth:request β€” the single-prompt form used by every code path that ships today. Handlers MAY additionally implement :meth:request_many to opt into the future batched-prompt delivery path (plan Β§3.4 / step 8): when the engine grows the per-session prompt queue, handlers exposing request_many get the full pending window in one call; handlers without it continue to be invoked one prompt at a time. v1 delivers prompts via request only β€” see tasks/prepare-for-codex-writing/03-batched-prompt-handler.md for the deferred implementation plan.

Methods

  • request Resolve one prompt; return the chosen action/payload.

source method PromptHandler.request(prompt: OnlinePrompt) β†’ PromptResponse

Resolve one prompt; return the chosen action/payload.

source class BatchedPromptHandler()

Bases : Protocol

A PromptHandler that also supports batched prompt delivery.

Codex’s defer-mode UI naturally yields a window of prompts the user will resolve together. When the engine gains the per-session prompt queue (step 8), it will prefer request_many over request for handlers structurally matching this Protocol.

Methods

  • request Resolve one prompt; return the chosen action/payload.

  • request_many Resolve a window of prompts in one call.

source method BatchedPromptHandler.request(prompt: OnlinePrompt) β†’ PromptResponse

Resolve one prompt; return the chosen action/payload.

source method BatchedPromptHandler.request_many(prompts: Sequence[OnlinePrompt]) β†’ Sequence[PromptResponse]

Resolve a window of prompts in one call.

Return one response per prompt, in the same order. The engine will not interleave request and request_many calls within a single drain window.

source class OnlineResult()

Per-file outcome of an online tagging call.

tags is the file’s full merged metadata, present even when the lookup applied nothing new (skip, no-match, deferred prompt) β€” it is NOT evidence of a match. matched is: it’s True only when an online source actually contributed metadata, so writers should gate on it to avoid re-writing a file with its own existing tags.

source class DeferredPrompt()

A prompt the session skipped under defer_prompts mode.

Captures everything Codex needs to render the prompt later in a review-tagging UI: the file it came from, source, candidates, mode context, and the fingerprint used to key the dedup cache. Codex feeds the user’s resolution back via :meth:OnlineSession.preload_resolution.

source class OnlineSession(*, sources: Iterable[str] = (‘metron’, ‘comicvine’), credentials: OnlineCredentials | None = None, mode: MatchMode = MatchMode.AUTO, unattended: bool = False, prompt_handler: PromptHandler | None = None, on_event: EventHandler | None = None, rematch: bool = False, first_wins: bool = True, defer_prompts: bool = False, series_batching: bool = True)

Codex-friendly faΓ§ade over Comicbox’s online lookup.

Construction validates per-source credentials and pre-computes the ComicboxSettings layer that each per-file Comicbox instance will see. Mutable state β€” mode, unattended, the cancel token β€” lives on the instance and may be updated from any thread.

Validate inputs, build per-session state. See class docstring.

Attributes

  • mode : MatchMode Current session mode (read-only; mutate via set_mode()).

  • unattended : bool Current session unattended flag.

  • cancelled : bool Whether cancel() has been called.

  • defer_prompts : bool Whether the session is currently in defer-prompts mode.

Methods

  • set_mode Change the session mode for subsequent file lookups.

  • set_unattended Toggle the unattended flag for subsequent file lookups.

  • cancel Stop accepting new files. In-flight lookup runs to completion.

  • set_defer_prompts Toggle defer-prompts mode for subsequent file lookups.

  • deferred_prompts Snapshot the queued deferred prompts. Codex’s review-UI input.

  • clear_deferred_prompts Drop the deferred-prompt queue without resolving them.

  • preload_resolution Seed the dedup cache so a re-run auto-resolves the fingerprint.

  • preload_series_resolution Seed the series cache from outside the session.

  • series_cache_snapshot Snapshot the current series cache. Useful for persistence.

  • clear_series_cache Drop every series resolution from the cache.

  • rate_limit_status Snapshot of each enabled source’s current rate-limit budget.

  • tag Tag one file synchronously.

  • tag_many Tag many files sequentially. Stops accepting new ones on cancel.

source property OnlineSession.mode: MatchMode

Current session mode (read-only; mutate via set_mode()).

source property OnlineSession.unattended: bool

Current session unattended flag.

source method OnlineSession.set_mode(mode: MatchMode) β†’ None

Change the session mode for subsequent file lookups.

source method OnlineSession.set_unattended(*, unattended: bool) β†’ None

Toggle the unattended flag for subsequent file lookups.

source method OnlineSession.cancel() β†’ None

Stop accepting new files. In-flight lookup runs to completion.

source property OnlineSession.cancelled: bool

Whether cancel() has been called.

source property OnlineSession.defer_prompts: bool

Whether the session is currently in defer-prompts mode.

source method OnlineSession.set_defer_prompts(*, defer: bool) β†’ None

Toggle defer-prompts mode for subsequent file lookups.

source method OnlineSession.deferred_prompts() β†’ tuple[DeferredPrompt, …]

Snapshot the queued deferred prompts. Codex’s review-UI input.

source method OnlineSession.clear_deferred_prompts() β†’ None

Drop the deferred-prompt queue without resolving them.

source method OnlineSession.preload_resolution(fingerprint: str, *, action: Literal[‘choose’, ‘skip’, ‘manual’], payload: int | str | None = None, chosen_volume_id: int | None = None) β†’ None

Seed the dedup cache so a re-run auto-resolves the fingerprint.

Codex’s defer-mode flow: drain deferred_prompts() after the batch, present them in a review UI, call preload_resolution() for each one the user resolves, then re-tag the affected files (with defer_prompts toggled off if desired). The cache hit fires the same way it does for in-batch dedup. A non-empty cache is enough to install the bridged selector β€” but with no handler and defer_prompts off, a cache MISS (the re-search produced a different candidate set than the fingerprint was minted from) raises rather than falling back to the interactive CLI prompt.

source method OnlineSession.preload_series_resolution(*, source: str, series_fingerprint: str, volume_id: int) β†’ None

Seed the series cache from outside the session.

Codex’s use case: persist resolved series across runs in its own DB and replay them at session-start to skip the cold-path search entirely on subsequent imports. Same first-writer-wins semantic as in-batch population β€” if the key is already cached, this is a no-op.

source method OnlineSession.series_cache_snapshot() β†’ dict[tuple[str, str], int]

Snapshot the current series cache. Useful for persistence.

source method OnlineSession.clear_series_cache() β†’ None

Drop every series resolution from the cache.

source method OnlineSession.rate_limit_status() β†’ dict[str, dict[str, Any]]

Snapshot of each enabled source’s current rate-limit budget.

v1 stub: returns an empty dict for every enabled source. The wiring to mokkari / simyan rate-limit buckets lands in the follow-up commit (see plan Β§3.5 / Β§3.6).

source method OnlineSession.tag(path: Path) β†’ OnlineResult

Tag one file synchronously.

source method OnlineSession.tag_many(paths: Iterable[Path]) β†’ Iterator[OnlineResult]

Tag many files sequentially. Stops accepting new ones on cancel.

When series_batching is on (the default), paths are reordered upfront by a lightweight filename-derived fingerprint so issues of the same series cluster together. The first issue of each cluster triggers the cold-path search; the remaining issues hit the per-session series cache and skip the search entirely. Order within a cluster is the original input order; the cluster order itself is deterministic (sorted by fingerprint) so re-runs produce identical cache-key sequences.