"""Executable scheduling example: one deployment run on a timer. This is the runnable companion to ``docs/deployment_scheduling.md`` ("hypothetically used as follows"): a local server plus the opt-in scheduler admit and complete one one-shot scheduled run of a constant workflow, then show its occurrence history. Run it with:: uv run pytest -q tests/examples/test_scheduled_deployment_example.py """ from __future__ import annotations import asyncio from datetime import UTC, datetime, timedelta from typing import Any from wf_api.models import RawWorkflowPlan from wf_core import END from wf_server import build_local_static_workflow_server from wf_server.scheduling import build_scheduler_service from wf_scheduling.lifecycle import SchedulerServiceConfig from wf_scheduling.store import FileScheduleStore def _constant_plan() -> RawWorkflowPlan: return RawWorkflowPlan.model_validate( { "name": "scheduled_hello", "input_schema": {"type": "object", "properties": {}}, "state_schema": { "type": "object", "properties": {"result": {"type": "string"}}, }, "output_schema": { "type": "object", "properties": {"result": {"type": "string"}}, "required": ["result"], }, "outcomes": ["ok"], "start": "constant", "nodes": [ { "id": "constant", "type": "node", "node": "wf.std.constant", "input": [ { "value": "hello on a schedule", "target": {"root": "local", "parts": ["value"]}, } ], "output": [ { "source": {"root": "local", "parts": ["value"]}, "target": {"root": "state", "parts": ["result"]}, } ], } ], "edges": [{"from": "constant", "outcome": "ok", "to": END}], "output": [ { "path": {"root": "state", "parts": ["result"]}, "target": {"root": "local", "parts": ["result"]}, } ], } ) async def _wait_for(condition: Any, timeout: float = 30.0) -> None: async with asyncio.timeout(timeout): while not condition(): await asyncio.sleep(0.02) async def test_scheduled_deployment_completes_on_a_timer(tmp_path: Any) -> None: root = tmp_path / "store" server = build_local_static_workflow_server(root, schedules=True) await server.api.create_artifact_from_plan( artifact_id="scheduled_hello", version=1, title="Scheduled Hello", plan=_constant_plan(), outcomes=["ok"], source_bindings={}, ) await server.api.save_deployment( { "id": "scheduled_hello.default", "artifact_id": "scheduled_hello", "artifact_version": 1, "bindings": {}, } ) due = datetime.now(UTC) + timedelta(seconds=0.5) created = await server.api.schedules.create_schedule( schedule_id="hello-once", deployment_id="scheduled_hello.default", trigger={"kind": "oneshot", "at": due.isoformat()}, ) assert created["revision"] == 1 service = build_scheduler_service( server, SchedulerServiceConfig(poll_interval_s=0.05, capacity=2), ) try: await service.start() def _completed() -> bool: runs = list(server.stores.run_store.list_runs()) return any(run.status.value == "completed" for run in runs) await _wait_for(_completed) run_id = next( run.id for run in server.stores.run_store.list_runs() if run.status.value == "completed" ) inspected = await server.api.inspect_run(run_id=run_id) assert inspected["status"] == "completed" assert (inspected["output"] or {})["result"] == "hello on a schedule" page = await server.api.schedules.list_schedule_occurrences( schedule_id="hello-once" ) kinds = [row["kind"] for row in page["occurrences"]] assert "admitted" in kinds assert "completed" in kinds finally: await service.stop() # The schedule definition survives its run; history is retained. assert FileScheduleStore(root).get_schedule("hello-once").exhausted is True