Skip to content

feat: provider plugin lifecycle management (closes #162)#170

Open
luceinaltis wants to merge 2 commits into
mainfrom
feat/162-provider-lifecycle
Open

feat: provider plugin lifecycle management (closes #162)#170
luceinaltis wants to merge 2 commits into
mainfrom
feat/162-provider-lifecycle

Conversation

@luceinaltis
Copy link
Copy Markdown
Owner

Summary

Closes #162.

Adds the missing lifecycle layer to the provider plugin system. Adapters (built-in or entry-point-discovered) can now opt into initialize() / health_check() / shutdown() hooks. _build_registries() runs the startup hooks before registration and skips unhealthy providers with a warning instead of letting them crash on first use; qracer serve runs the teardown hooks on exit.

All three hooks are optional — existing adapters (YfinanceAdapter, FinnhubAdapter, FredAdapter, ClaudeAdapter, OpenAIAdapter, GeminiAdapter) keep working unchanged because the helpers duck-type each method.

What changed

  • qracer/provider_lifecycle.py (new) — self-contained lifecycle module:

    • LifecycleProvider — runtime-checkable Protocol covering all three hooks.
    • initialize_provider(name, adapter) — async helper. Runs initialize() then health_check(); returns False when a hook raises or reports unhealthy (both paths logged as warnings). Adapters without hooks are always True.
    • shutdown_provider(name, adapter) — async helper. Swallows exceptions so a bad provider can't block the rest of the teardown sweep.
    • initialize_provider_sync / shutdown_all_providers_sync — sync wrappers for use from CLI setup code. Detect an already-running loop and no-op instead of blowing up (defensive — the real call sites are sync).
    • shutdown_all_providers(*registries) — walks the _adapters / _providers buckets of DataRegistry and LLMRegistry, deduping adapters registered under multiple capabilities/roles.
  • qracer/cli.py::_build_registries — for both data- and llm-provider branches, call initialize_provider_sync(name, adapter) after instantiation. If it returns False, append a human-readable warning and skip registration. Existing credential-missing and import-error paths are untouched.

  • qracer/cli.py::serve — wraps asyncio.run(server.run()) in a finally that calls shutdown_all_providers_sync(data_registry, llm_registry). A second try/except guards the shutdown itself so the PID-file release always runs.

  • docs/architecture.md — replaces the "구현 예정" note in the Provider Plugin System section with the actual LifecycleProvider protocol, a working third-party adapter example (PolygonAdapter), and the updated startup + shutdown flow diagram.

Scope mapping (#162)

Scope item Status
Add initialize() / health_check() / shutdown() protocol LifecycleProvider in qracer/provider_lifecycle.py
Wire providers.toml to provider_catalog.py ✅ already in place via _build_registries() (config.providers.providers loop)
Call lifecycle hooks in _build_registries() and Server.shutdown() ✅ init+health in _build_registries; shutdown in serve() finally
Graceful degradation on health_check() failure ✅ warning + skip instead of crash
Example third-party provider entry point in docs PolygonAdapter example in docs/architecture.md

Test plan

  • uv run pytest801 passed, 14 skipped (23 new lifecycle tests).
  • uv run pytest tests/test_provider_lifecycle.py tests/test_cli.py -x — 24 passed.
  • uv run ruff check — all checks passed.
  • uv run pyright qracer/provider_lifecycle.py tests/test_provider_lifecycle.py — 0 errors. (qracer/cli.py reports one pre-existing uvicorn import warning unrelated to this change — reproduced on main.)

New tests cover

  • Protocol detection — async adapter matches LifecycleProvider, no-hook adapter does not.
  • initialize_provider — async + sync hook styles, both hooks absent, unhealthy health_check, initialize raises (health_check skipped), health_check raises.
  • shutdown_provider — async + sync hook styles, no-hook no-op, exception swallowed.
  • Sync wrappers — delegate to async helpers; running-loop branch is a safe no-op; both initialize_provider_sync and shutdown_all_providers_sync exercised under a live loop.
  • shutdown_all_providers — dedupes an adapter registered under multiple capabilities; supports both _adapters (DataRegistry-style) and _providers (LLMRegistry-style) buckets; empty registry is a no-op; mixed data+llm registries handled in a single call; one failing sibling doesn't prevent others from shutting down.

Manual verification

from qracer.data.registry import DataRegistry
from qracer.provider_lifecycle import initialize_provider_sync, shutdown_all_providers_sync

class Healthy:
    async def initialize(self): print("init")
    async def health_check(self): return True
    async def shutdown(self): print("shutdown")

reg = DataRegistry()
adapter = Healthy()
assert initialize_provider_sync("demo", adapter) is True   # prints "init"
reg.register("demo", adapter, [])
shutdown_all_providers_sync(reg)                           # prints "shutdown"

claude added 2 commits April 15, 2026 02:09
Add optional initialize/health_check/shutdown hooks for data and LLM
providers. Adapters without hooks remain supported — the helpers
duck-type each method so partial implementations are fine.

- New qracer/provider_lifecycle.py exposes LifecycleProvider protocol
  plus async/sync helpers (initialize_provider, shutdown_provider,
  shutdown_all_providers).
- _build_registries() now runs initialize + health_check on each
  adapter before registration; failures are warned and the provider is
  excluded instead of crashing the registry build.
- qracer serve's finally block calls shutdown_all_providers_sync so
  long-lived adapters (HTTP sessions, WebSockets) get a clean teardown
  on signal.
- 23 new tests cover protocol detection, async/sync hooks, unhealthy
  and raising paths, dedup across shared adapters, and the running-loop
  guard in the sync wrappers.
- Architecture docs refreshed with a working third-party adapter
  example and the startup/shutdown flow.
Collapse a multi-line function signature that was over-wrapped for the
CI ruff-format check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: provider plugin lifecycle management (init/health_check/shutdown)

2 participants