refactor: move workflow command handlers to workflows/_commands.py (PR-8/8)#3159
Open
darion-yaphet wants to merge 3 commits into
Open
refactor: move workflow command handlers to workflows/_commands.py (PR-8/8)#3159darion-yaphet wants to merge 3 commits into
darion-yaphet wants to merge 3 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors the CLI by extracting all specify workflow * Typer apps and handlers from src/specify_cli/__init__.py into a dedicated src/specify_cli/workflows/_commands.py, re-attached via a register(app) shim to keep the CLI interface intact.
Changes:
- Moved all workflow command groups/handlers (run/resume/status, catalogs, step catalogs) into
workflows/_commands.py. - Updated
src/specify_cli/__init__.pyto register the workflow command group viaworkflows._commands.register(app). - Updated workflow tests to import moved private helpers (
_gate_outcome,_stdout_to_stderr_when) from the new module path.
Show a summary per file
| File | Description |
|---|---|
tests/test_workflows.py |
Updates imports to reference workflow helper functions in the new specify_cli.workflows._commands module. |
src/specify_cli/workflows/_commands.py |
New module containing the extracted workflow Typer apps, handlers, and helper functions plus register(app). |
src/specify_cli/__init__.py |
Removes inlined workflow command handlers and registers the workflow command group via the new module. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 2
Comment on lines
+871
to
+874
| def workflow_catalog_add( | ||
| url: str = typer.Argument(..., help="Catalog URL to add"), | ||
| name: str = typer.Option(None, "--name", help="Catalog name"), | ||
| ): |
Comment on lines
+1523
to
+1526
| def workflow_step_catalog_add( | ||
| url: str = typer.Argument(..., help="Catalog URL to add"), | ||
| name: str = typer.Option(None, "--name", help="Catalog name"), | ||
| ): |
mnriem
requested changes
Jun 25, 2026
mnriem
left a comment
Collaborator
There was a problem hiding this comment.
Please address Copilot feedback
a900a0a to
ed73f34
Compare
Comment on lines
+480
to
+483
| project_root = _require_specify_project() | ||
| registry = WorkflowRegistry(project_root) | ||
| workflows_dir = project_root / ".specify" / "workflows" | ||
|
|
Collaborator
|
Please address Copilot feedback |
86ff4db to
938a389
Compare
Comment on lines
+52
to
+54
| def _require_specify_project(*args, **kwargs): | ||
| from .. import _require_specify_project as _f | ||
| return _f(*args, **kwargs) |
Comment on lines
+244
to
+246
| project_root = Path.cwd() | ||
| _reject_unsafe_dir(project_root / ".specify", ".specify") | ||
| else: |
Collaborator
|
Please address Copilot feedback |
…R-8/8) Final PR of the __init__.py split. Moves the workflow command group out of __init__.py into the existing workflows/ package, completing the domain-dir layout established in PR-5 (integrations), PR-6 (presets) and PR-7 (extensions). - New workflows/_commands.py holds the four Typer apps (workflow / catalog / step / step-catalog), all 25 command handlers, the six workflow-only helpers (_parse_input_values, _workflow_run_payload, _emit_workflow_json, _stdout_to_stderr_when, _validate_step_id_or_exit, _resolve_steps_base_dir_or_exit), and a register(app) entry point. - workflows is already a package, so no rename is needed; intra-package imports change from `.workflows.x` to `.x`. The only root-helper dep (_require_specify_project) is reached through a call-time shim so test monkeypatching of specify_cli._require_specify_project keeps working. - __init__.py drops ~1445 lines (2066 -> 621); the workflow group is re-attached via register(app). Dead `contextlib` import removed. - tests/test_workflows.py: import the now-relocated _stdout_to_stderr_when helper from its new home (workflows._commands) instead of the package root. No behavior change. Full suite green (3847 passed), ruff clean.
Workflow commands persist run state under .specify/workflows/runs, so the command-local project shim now rejects symlinked workflow storage before any workflow command proceeds. The standalone YAML path uses the same guard because it intentionally bypasses the normal project requirement while still creating workflow state under the current directory. Constraint: Local YAML workflow runs do not require an existing .specify project directory but still create .specify/workflows/runs state Rejected: Guard only .specify in the file-source path | .specify/workflows and runs can independently redirect writes Confidence: high Scope-risk: narrow Directive: Keep workflow storage symlink checks centralized before constructing WorkflowEngine Tested: .venv/bin/python -m pytest tests/test_workflow_run_without_project.py tests/test_workflows.py::TestWorkflowAddSymlinkGuard -v Tested: .venv/bin/python -m py_compile src/specify_cli/workflows/_commands.py tests/test_workflow_run_without_project.py tests/test_workflows.py Not-tested: Ruff lint; ruff is not installed in the repo virtualenv Assisted-by: OpenAI Codex (model: GPT-5, autonomous)
938a389 to
95d8df5
Compare
Comment on lines
+562
to
+565
| from specify_cli._github_http import resolve_github_release_asset_api_url as _resolve_gh_asset | ||
|
|
||
| _wf_url_extra_headers = None | ||
| _resolved_wf_url = _resolve_gh_asset(source, _open_url, timeout=30) |
Comment on lines
+668
to
+672
| from specify_cli.authentication.http import open_url as _open_url | ||
| from specify_cli._github_http import resolve_github_release_asset_api_url as _resolve_gh_asset | ||
|
|
||
| _wf_cat_extra_headers = None | ||
| _resolved_workflow_url = _resolve_gh_asset(workflow_url, _open_url, timeout=30) |
Collaborator
|
Please address Copilot feedback |
…olver workflow add resolved GitHub release download URLs without forwarding the github_provider_hosts() allowlist, so resolve_github_release_asset_api_url never treated any host as GHES. This regressed GitHub Enterprise Server release asset resolution and diverged from presets/extensions, which already pass github_hosts. Forward github_provider_hosts() at both the direct-URL and catalog install call sites. The allowlist remains the anti-SSRF gate.
Comment on lines
+531
to
+534
| dest_dir = workflows_dir / definition.id | ||
| dest_dir.mkdir(parents=True, exist_ok=True) | ||
| import shutil | ||
| shutil.copy2(yaml_path, dest_dir / "workflow.yml") |
Comment on lines
+683
to
+684
| workflow_dir.mkdir(parents=True, exist_ok=True) | ||
| with _open_url(workflow_url, timeout=30, extra_headers=_wf_cat_extra_headers) as response: |
Collaborator
|
Please address Copilot feedback |
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.
Summary
Final PR in the
__init__.pysplit series (PR-8/8). Moves allworkflowcommand handlers out ofsrc/specify_cli/__init__.pyinto a dedicatedsrc/specify_cli/workflows/_commands.py, registered back viaregister(app)to preserve the exact CLI surface.With this,
src/specify_cli/__init__.pydrops from ~5914 lines (series start) to 642 lines.Changes
workflow/workflow catalog/workflow stepTyper apps and all handlers (run,resume,status, list/show/add/remove for catalogs and steps) →workflows/_commands.py.__init__.pynow just importsregisterfromworkflows._commandsand calls it — CLI behavior unchanged.main: ported the gate-outcome / structured-JSON and process-exit-code featuresmainadded toworkflow run/resumeafter this branch's base (_workflow_run_payloadgate block,_is_gate_step,_gate_outcome,_normalize_gate_options,_run_outcome_exit_code) into the new module._gate_outcomeimports to the new module path (specify_cli.workflows._commands), matching the convention used by other moved-helper tests.Test plan
pytest tests/test_workflows.py— 308 passedmain(offline/network-dependent), not introduced hereDepends on / follows #3014 (PR-7), now merged.