feat: add prefect_enabled config option to run ingestions without Prefect - #336
Open
minhajuddin2510 wants to merge 1 commit into
Open
feat: add prefect_enabled config option to run ingestions without Prefect#336minhajuddin2510 wants to merge 1 commit into
prefect_enabled config option to run ingestions without Prefect#336minhajuddin2510 wants to merge 1 commit into
Conversation
… Prefect Submitting a resource fails outright on deployments where CKAN cannot write $PREFECT_HOME: ERROR [ckanext.datapusher_plus.logic.action] Error submitting job to DataPusher: [Errno 13] Permission denied: '/root/.prefect/profiles.toml' That is not a Prefect outage — `submit_flow_run`'s `from prefect.deployments import run_deployment` runs Prefect's settings bootstrap, which creates `$PREFECT_HOME/profiles.toml` (default `$HOME/.prefect`). A CKAN process running with HOME=/root but no write access there cannot even import the library, so no amount of server configuration helps. Add `ckanext.datapusher_plus.prefect_enabled` (default true). Setting it to false turns Prefect off entirely: `datapusher_submit` enqueues the job on CKAN's own background queue and the new `jobs/local_runner.py` runs the same nine ingestion stages in-process, with nothing on the path importing `prefect`. Operators run `ckan jobs worker` instead of a Prefect server, worker, and work pool. The local runner keeps the Jobs/Logs tables, the datapusher_hook callbacks, the complete-with-skip contract, the flow_timeout deadline, and the post-database-failure datastore cleanup. It does not provide per-stage retries, result caching, the run graph/artifacts/events, or human-in-the-loop PII review — with Prefect off, a job crossing pii_review_threshold aborts before any datastore write rather than suspending for an approval that can never arrive. A database-stage failure deliberately leaves the datastore alone (that stage can fail before touching anything, and dropping there would destroy data the run never wrote). The task_status row records the RQ job id as `rq_job_id` rather than `flow_run_id`, so nothing builds a Prefect-UI deep link out of it. The Prefect-free half of the pipeline moves out of prefect_flow.py into the new jobs/pipeline_core.py — the CKAN status callback, input validation, RuntimeContext construction, the stage invoker and its StageAbort signal, and the datastore rollback body — shared verbatim by both runners. prefect_flow keeps its historical private names as aliases, so custom flows composed from its @task primitives are unaffected; tests that patched prefect_flow.dsu / .QSVCommand / .Path now patch pipeline_core. Unit suite: 307 passed, 1 failed (test_quoted_csv_inference_matrix, a pre-existing qsv-binary-dependent failure present on the base commit too). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a ckanext.datapusher_plus.prefect_enabled configuration switch (default true) to support running DP+ ingestions without importing Prefect at all, falling back to CKAN’s RQ worker for orchestration when disabled. It introduces a Prefect-free shared pipeline core so both the Prefect flow and the local runner reuse the same callback/validation/context/rollback logic.
Changes:
- Add
prefect_enabledconfig + declarative config entry, and routedatapusher_submitto either Prefect (prefect_client) or RQ (jobs/local_runner.py). - Introduce
jobs/pipeline_core.py(Prefect-free shared pipeline helpers) andjobs/local_runner.py(in-process 9-stage runner + RQ helpers). - Update docs and tests to cover the no-Prefect mode and refactored patch targets.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_prefect_flow.py | Updates patch targets to pipeline_core after refactor. |
| tests/test_local_runner.py | New unit coverage for prefect_enabled=false path, including “no Prefect import” invariant. |
| tests/test_dictionary_stash.py | Updates monkeypatch targets to pipeline_core datastore helpers. |
| README.md | Documents prefect_enabled option, no-Prefect mode behavior, and troubleshooting. |
| CONFIG.md | Adds configuration guidance for disabling Prefect and operational requirements. |
| CLAUDE.md | Updates architecture notes to include pipeline_core + local_runner. |
| ckanext/datapusher_plus/plugin.py | Logs an operator-facing startup INFO line when Prefect is disabled. |
| ckanext/datapusher_plus/logic/action.py | Adds _orchestrator() / _submit_job() routing and records rq_job_id vs flow_run_id. |
| ckanext/datapusher_plus/jobs/prefect_flow.py | Moves Prefect-free functionality into pipeline_core and aliases historical private names. |
| ckanext/datapusher_plus/jobs/pipeline_core.py | New Prefect-free shared implementation (callback, validate, context build, stage invocation, rollback, int resolution). |
| ckanext/datapusher_plus/jobs/local_runner.py | New RQ-backed in-process ingestion runner + queue scanning helpers. |
| ckanext/datapusher_plus/jobs/events.py | Makes event emission a no-op when Prefect is disabled (to avoid importing Prefect). |
| ckanext/datapusher_plus/jobs/init.py | Exposes run_job and ensures callback helper is sourced from Prefect-free core. |
| ckanext/datapusher_plus/config.py | Adds prefect_enabled() runtime config accessor. |
| ckanext/datapusher_plus/config_declaration.yaml | Declares ckanext.datapusher_plus.prefect_enabled with default and description. |
| ckanext/datapusher_plus/cli.py | Prevents prefect-deploy when Prefect is disabled; adjusts migrate messaging/checks. |
| CHANGELOG.md | Documents the new configuration option and refactor. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+186
to
+191
| for job in jobs: | ||
| args = job.args or () | ||
| payload = args[0] if args else None | ||
| if isinstance(payload, dict) and payload.get("resource_id"): | ||
| ids.add(payload["resource_id"]) | ||
| except Exception as e: |
Comment on lines
+384
to
+396
| try: | ||
| run_stage(IndexingStage()) | ||
| run_stage(FormulaStage()) | ||
| run_stage(MetadataStage()) | ||
| except Exception: | ||
| # Catches ``StageAbort`` too — one of these stages | ||
| # returning ``None`` after the load has committed leaves | ||
| # the same half-finished table as a raise, and under | ||
| # Prefect it would likewise fail the transaction and | ||
| # fire the rollback hooks. The outer handler still marks | ||
| # the job complete-with-skip. | ||
| rollback_datastore_writes(runtime) | ||
| raise |
Comment on lines
+476
to
+480
| if not conf.prefect_enabled(): | ||
| click.echo( | ||
| "ckanext.datapusher_plus.prefect_enabled is false — skipping " | ||
| "the Prefect reachability check; jobs will run in-process on " | ||
| "CKAN's RQ worker (`ckan jobs worker`)." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Submitting a resource fails outright on deployments where CKAN cannot write
$PREFECT_HOME:This is not a Prefect outage.
prefect_client.submit_flow_run()doesfrom prefect.deployments import run_deployment, and that import runs Prefect's settings bootstrap, which creates$PREFECT_HOME/profiles.toml(default$HOME/.prefect). A CKAN process running withHOME=/rootbut no write access there cannot even import the library, so no server configuration fixes it.What changed
New setting, default
true:ckanext.datapusher_plus.prefect_enabled = falseWith it off,
datapusher_submitenqueues the job on CKAN's own background queue and the newjobs/local_runner.pyexecutes the same nine ingestion stages in-process — no Prefect server, worker, work pool, orimport prefectanywhere on the path. Operators runckan jobs workerinstead.enqueue_job(RQ submit),run_job(the nine stages),get_running_resource_ids(RQ-queue scan for the duplicate-submission check)jobs/local_runner.py(new)RuntimeContextconstruction, stage invoker +StageAbort, datastore rollback,resolve_intjobs/pipeline_core.py(new)_orchestrator()/_submit_job()routinglogic/action.pyprefect-deployrefuses to run in this mode;migrate-from-rqskips the reachability check; startup log linejobs/events.py,cli.py,plugin.pyUnchanged in local mode:
Jobs/Logstables, the job-status page,datapusher_hookcallbacks (running → complete/error), CLI submit/resubmit, DRUF, the complete-with-skip contract, and theflow_timeoutdeadline (also passed to RQ as the job timeout — RQ's own default is 180s, which would kill any real ingestion mid-COPY).Reviewer notes — three deliberate behavior decisions
pii_review_threshold > 0asks a human to approve a PII-flagged run. Nothing can hold a job open without Prefect, so the only safe reading of that intent is "do not load it" — the job errors before any datastore write. Documented in the README table and in the error message itself.database_task.on_rollback. But the database stage can raise before touching anything ("Could not connect to the Datastore"), and dropping there would destroy intact data the run never wrote. Both paths are tested.rq_job_id, notflow_run_id. Otherwise a deployment with a staleprefect_ui_basewould render a Prefect UI deep-link pointing at an RQ id.datapusher_statusalready returnsjob_url: Nonewhenflow_run_idis absent, so its response shape is unchanged.Refactor note
prefect_flow.pyshed the Prefect-free half intopipeline_core.py(~200 lines moved, not rewritten) so there is one implementation per behavior.prefect_flowbinds the moved names to its historical private spellings (_StageAbort,_build_runtime_context,_stage_run, …), so custom flows composed from its@taskprimitives are unaffected. Tests that patchedprefect_flow.dsu/.QSVCommand/.Pathnow patchpipeline_core— same module objects, owner-correct target.Testing
Unit suite (
pytest tests/ --ignore=tests/integration): 307 passed, 1 failed vs 279 passed, 1 failed on the base commit. The single failure istest_quoted_csv_inference_matrix, pre-existing and unrelated (needs a real qsv 20.x binary locally).28 new tests in
tests/test_local_runner.py: the config switch, stage order, complete-with-skip, datastore-dump short-circuit, both rollback paths, both PII-gate paths, the RQ helpers, anddatapusher_submitrouting (including which key the task_status row gets). One of them shells out to a subprocess to assert that importing the disabled path pulls in zeroprefectmodules — the invariant this whole PR rests on.Integration tests were not run (no docker-compose stack in the dev environment); a reviewer with the stack up should sanity-check that the default Prefect path is untouched end-to-end.
Alternative for operators who want to keep Prefect
Point
PREFECT_HOMEat a directory the CKAN user owns (e.g./var/lib/ckan/prefect) in both the CKAN and worker environments. Documented alongside the new mode in the README troubleshooting table.🤖 Generated with Claude Code