Skip to content

feat(core): add Application.run() to invoke registered commands programmatically#144

Open
bedus-creation wants to merge 6 commits into
mainfrom
task/application-run-method
Open

feat(core): add Application.run() to invoke registered commands programmatically#144
bedus-creation wants to merge 6 commits into
mainfrom
task/application-run-method

Conversation

@bedus-creation

Copy link
Copy Markdown
Contributor

Summary

Adds a public run() method to the core Application class that executes an already-registered console command by name from Python code — a programmatic API, not a new artisan CLI command. Returns the command's exit code.

app.run("db:migrate")
app.run("db:seed", "--force")
app.run("db:seed", ["--force"])   # list args also accepted

Implementation

  • Application.run(command: str, args: str | list[str] | None = None) -> int in application.py.
  • Reuses existing console wiring: mirrors handle_command() by booting ConsoleApplication(self).
  • Dispatches via Cleo with a constructed StringInput (no sys.argv), and disables auto_exit so the exit code is returned instead of terminating the host process.
  • args accepts a string or a list/tuple (joined with shlex.join); None runs the command with no extra arguments.

Tests

tests/console/test_application_run.py — registers a dummy command on a fresh Application and covers:

  • zero exit code returned
  • non-zero exit code propagated
  • no-args invocation
  • string args forwarding ("hello --force")
  • list args forwarding (["hello", "--force"])

The fixture restores the previous container singleton on teardown. All 5 pass; console/core/serve suites (144 tests) remain green. Verified end-to-end against the real provider:publish command.

Context

Supersedes the closed PR #143, which added a CLI run command — the wrong interpretation. This delivers the intended programmatic API instead.

…ammatically

Expose a public run() method on the core Application that executes an
already-registered console command by name from Python, without going
through sys.argv, and returns the command exit code.

  app.run("db:migrate")
  app.run("db:seed", "--force")
  app.run("db:seed", ["--force"])

Reuses the existing ConsoleApplication wiring (mirrors handle_command),
dispatching via Cleo with a constructed StringInput and auto_exit disabled
so the code is returned rather than terminating the host process.

@bedus-creation bedus-creation left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review verdict: APPROVE (posting as a comment — GitHub blocks formal self-approval since the authenticated account authored this PR). Verified locally on branch task/application-run-method.

This is the correct deliverable (programmatic Application.run(), superseding the CLI-command approach in #143).

Review against the requested dimensions:

  1. Clean addition reusing console wiring (mirrors handle_command) ✅ — run() builds ConsoleApplication(self) exactly like handle_command(), then dispatches via Cleo with an explicit StringInput (no sys.argv).
  2. No unintended core abstraction changes ✅ — purely additive: one import shlex + one new method in application.py. No existing lines modified; no Container/Provider/Model changes.
  3. Correct auto_exit handling ✅ — console.auto_exits(False) is the correct Cleo API; verified the exit code is returned, not sys.exit-ed.
  4. Catchable unknown-command error ✅ — app.run("does:not:exist") renders a clean Cleo error and returns exit code 1; no SystemExit, host process not terminated. Verified empirically.
  5. Meaningful tests ✅ — 5 tests (zero exit, non-zero propagation, no-args, string args, list args); fixture restores the previous Container singleton on teardown. All pass; full console+core suite (126 tests) green; ruff clean.
  6. Focused diff ✅ — 3 files, no uv.lock, no version bumps.

Non-blocking nit (optional): consider one test pinning the unknown-command path (returns non-zero, no SystemExit), since that's currently verified only manually.

Holding merge until Console QA reports PASS on task #481, per PM.

Add cases for exit-code type, unknown command, None/empty args, short
options, list/tuple args, space-preserving and stringified values, and
repeat invocation.
Resolve the command via ConsoleApplication.find() before dispatch so an
unknown name raises CleoCommandNotFoundError that callers can catch, instead
of being swallowed into exit code 1 by Cleo's non-auto-exit run loop.
Known-command behavior (returned exit code, no process exit) is unchanged.
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.

1 participant