Phase 7: serve the vet sample from the template tools and docs
- build.py: _TEMPLATE_COMPONENTS rewritten around the vet sample (53 kinds —
per-resource slices, jobs, worked appointment_reminder family, tests);
seed tokens dropped from the manifest ('sample: vet' instead)
- registry.py: get_app_component(kind) no longer renames item/example seeds;
prompts and tool docs updated to 'copy a slice and adapt'
- templates/README.md, 01_build_recipe, 40_ui_admin, wiki/03 updated off the
old seed scheme; repo inventory/config now exclude vet (docs move under
standards; react/vet itself is deleted in a later phase)
- regenerated MCP bundle, vendored templates, and site output
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""# Summary
|
||||
|
||||
Background worker — polls for pending jobs and runs the item-sync pipeline.
|
||||
The poll loop is the pipeline axis of the blueprint: resources are created by
|
||||
the API axis, workers consume them on a fixed cadence.
|
||||
Background worker — polls for pending jobs and runs the matching pipeline
|
||||
family. The poll loop is the pipeline axis of the sample: resources are created
|
||||
by the API axis, workers consume them on a fixed cadence.
|
||||
|
||||
Notes:
|
||||
- This process is separate from the API process. Any in-memory event bus is per
|
||||
@@ -11,9 +11,10 @@ Notes:
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
from config import settings
|
||||
from pipelines.item_sync import run_item_sync
|
||||
from pipelines.appointment_reminder.impl import get_appointment_reminder_pipeline
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -22,9 +23,29 @@ logging.basicConfig(
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# job.kind -> async (job, resources) -> None. Add families here as they land.
|
||||
HANDLERS: dict[str, Callable[..., Awaitable[None]]] = {}
|
||||
|
||||
async def poll_jobs(resources: dict):
|
||||
"""Poll for pending jobs and run the item-sync pipeline on each.
|
||||
|
||||
def _register_default_handlers() -> None:
|
||||
"""Populate the dispatch table with the shipped families."""
|
||||
|
||||
async def run_appointment_reminder(job, resources: dict[str, Any]) -> None:
|
||||
payload = job.payload or {}
|
||||
appointment_id = payload.get("appointment_id")
|
||||
if appointment_id is None:
|
||||
raise ValueError("appointment_reminder job requires payload.appointment_id")
|
||||
pipeline = get_appointment_reminder_pipeline(resources["appointment_svc"])
|
||||
await pipeline.run(int(appointment_id))
|
||||
|
||||
HANDLERS["appointment_reminder"] = run_appointment_reminder
|
||||
|
||||
|
||||
_register_default_handlers()
|
||||
|
||||
|
||||
async def poll_jobs(resources: dict) -> None:
|
||||
"""Poll for pending jobs and dispatch each to its pipeline family.
|
||||
|
||||
Args:
|
||||
resources (dict): Wired services from :func:`init.init`.
|
||||
@@ -34,8 +55,9 @@ async def poll_jobs(resources: dict):
|
||||
settings.worker_poll_seconds, settings.worker_max_retries)
|
||||
|
||||
while True:
|
||||
pending = await job_svc.search(filter={"status": "pending"}, limit=10)
|
||||
logger.info("Found %d pending job(s)", len(pending) if pending else 0)
|
||||
pending = await job_svc.search({"status": "pending"})
|
||||
pending = pending[:10]
|
||||
logger.info("Found %d pending job(s)", len(pending))
|
||||
|
||||
for job in pending:
|
||||
job_id = str(job.id)
|
||||
@@ -43,13 +65,20 @@ async def poll_jobs(resources: dict):
|
||||
logger.warning("SKIPPING job %s — retries exhausted", job_id)
|
||||
continue
|
||||
|
||||
handler = HANDLERS.get(job.kind)
|
||||
await job_svc.patch(job_id, {"status": "processing"})
|
||||
try:
|
||||
await run_item_sync(job, resources)
|
||||
if handler is None:
|
||||
raise ValueError(f"no handler for job kind '{job.kind}'")
|
||||
await handler(job, resources)
|
||||
await job_svc.patch(job_id, {"status": "done", "error_message": ""})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.exception("job %s FAILED: %s", job_id, exc)
|
||||
await job_svc.patch(job_id, {"status": "failed", "error_message": str(exc)})
|
||||
await job_svc.patch(job_id, {
|
||||
"status": "failed",
|
||||
"error_message": str(exc),
|
||||
"retry_count": (job.retry_count or 0) + 1,
|
||||
})
|
||||
|
||||
await asyncio.sleep(settings.worker_poll_seconds)
|
||||
|
||||
@@ -64,4 +93,4 @@ if __name__ == "__main__":
|
||||
finally:
|
||||
await shutdown()
|
||||
|
||||
asyncio.run(_run())
|
||||
asyncio.run(_run())
|
||||
|
||||
Reference in New Issue
Block a user