Skip to content

feat(cli): add py script type & Python interpreter resolution (#3278)#3285

Open
mnriem wants to merge 4 commits into
github:mainfrom
mnriem:mnriem-feat-3278-py-script-type
Open

feat(cli): add py script type & Python interpreter resolution (#3278)#3285
mnriem wants to merge 4 commits into
github:mainfrom
mnriem:mnriem-feat-3278-py-script-type

Conversation

@mnriem

@mnriem mnriem commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements #3278 — the foundation step toward unifying workflow scripts under a single Python implementation per command (#3277). Adds a third script variant (py) alongside sh/ps.

Changes

  • New choice: Add "py": "Python" to SCRIPT_TYPE_CHOICES in _agent_config.py. All VALID_SCRIPT_TYPES consumers (the init workflow step, commands/init.py, and integrations/_helpers.py) derive from this mapping, so they accept py without further change.
  • process_template() (integrations/base.py): Confirmed the existing generic script_type logic extracts py: from frontmatter unchanged. When the script type is py, the {SCRIPT} expansion is now prefixed with a resolved Python interpreter so .py scripts run portably (notably on Windows, where .py files aren't directly executable). sh/ps go down the identical path as before.
  • Interpreter resolution: New IntegrationBase.resolve_python_interpreter() helper — prefers a project venv (returned relative to the project root: .venv/bin/python or .venv/Scripts/python.exe), then python3 on PATH, then python on PATH, then the running interpreter (sys.executable), falling back to python3 only if even that is unavailable. project_root is threaded through the process_template() callers so the venv preference works. When the resolved interpreter path contains whitespace (e.g. Windows Program Files) it is quoted so the {SCRIPT} invocation isn't split into multiple args.
  • install_scripts(): Copied .py files are now marked executable (previously .sh-only; .ps1 behavior unchanged).

Out of scope

Adding py: lines to the templates' scripts: frontmatter is tracked separately in #3283. Until then, {SCRIPT} is left literal for py (existing graceful fallback when no matching frontmatter line exists).

Tests

Added positive and negative unit tests:

  • Interpreter resolution: prefers python3, falls back to python, falls back to sys.executable when PATH resolution fails (and to python3 only if that is empty), prefers POSIX/Windows venv (relative path), ignores a missing venv.
  • Interpreter quoting: a whitespace-containing interpreter path is quoted; a whitespace-free one is not.
  • process_template with py: prefixes interpreter + rewrites path; sh is not prefixed; venv is used when project_root is passed.
  • install_scripts: .py/.sh made executable, non-script file is not.
  • New choice present in SCRIPT_TYPE_CHOICES / VALID_SCRIPT_TYPES; unknown variant rejected.

Verification

  • specify init x --script py completes without error and records "script": "py".
  • Full suite: 3860 passed, 4 skipped. ruff check clean on changed files.

🤖 This PR was authored autonomously by GitHub Copilot (model: Claude Opus 4.8) on behalf of @mnriem.

…ub#3278)

Introduce a third script variant alongside `sh`/`ps` as the foundation
for unifying workflow scripts under a single Python implementation.

- Add `"py": "Python"` to `SCRIPT_TYPE_CHOICES`; `VALID_SCRIPT_TYPES`
  consumers (init workflow step, init command, _helpers) pick it up
  automatically since they derive from that mapping.
- Add `IntegrationBase.resolve_python_interpreter()` (project venv →
  `python3` → `python`, falling back to `python3`).
- Prefix the resolved interpreter when `process_template()` expands
  `{SCRIPT}` for the `py` script type so `.py` scripts run portably
  (notably on Windows); thread `project_root` through callers so venv
  preference works.
- Make `install_scripts()` mark copied `.py` files executable too.

Includes positive and negative unit tests for interpreter resolution,
`py` template processing, the new choice, and script installation.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 21:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class support for a Python (py) workflow script variant alongside existing sh/ps options, including template rendering support that prefixes Python scripts with a resolved interpreter for cross-platform execution.

Changes:

  • Adds "py": "Python" to SCRIPT_TYPE_CHOICES so all VALID_SCRIPT_TYPES consumers accept py.
  • Updates IntegrationBase.process_template() to prefix {SCRIPT} with a resolved Python interpreter when script_type == "py", and threads project_root through integration setup call sites so venv preference can work.
  • Extends install_scripts() to mark copied .py scripts executable, and adds unit tests for interpreter resolution, template processing, and chmod behavior.
Show a summary per file
File Description
tests/test_commands_package.py Verifies py is present in script type choices and workflow init valid script types.
tests/integrations/test_base.py Adds unit coverage for interpreter resolution, py template rendering behavior, and .py chmod in install_scripts().
src/specify_cli/_agent_config.py Introduces py as a supported script type choice.
src/specify_cli/integrations/base.py Implements Python interpreter resolution, py {SCRIPT} prefixing, threads project_root, and chmods .py scripts.
src/specify_cli/integrations/copilot/init.py Passes project_root into template processing for venv-aware interpreter selection.
src/specify_cli/integrations/forge/init.py Passes project_root into template processing for venv-aware interpreter selection.
src/specify_cli/integrations/generic/init.py Passes project_root into template processing for venv-aware interpreter selection.
src/specify_cli/integrations/hermes/init.py Passes project_root into template processing for venv-aware interpreter selection.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment thread src/specify_cli/integrations/base.py
Comment thread src/specify_cli/integrations/base.py Outdated
Address PR review feedback on github#3285:

- `resolve_python_interpreter()` now returns the venv interpreter as a
  path relative to the project root (`.venv/bin/python` /
  `.venv/Scripts/python.exe`) instead of an absolute/joined path, so the
  generated `{SCRIPT}` invocation stays portable and runnable from the
  repo root regardless of where the project lives.
- Update `install_scripts()` docstring to note `.py` scripts are now
  made executable alongside `.sh`.
- Update tests to assert the repo-relative interpreter path.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mnriem

mnriem commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both review comments in 24c0e2f:

  • resolve_python_interpreter() portability: now returns the venv interpreter as a path relative to the project root (.venv/bin/python or .venv/Scripts/python.exe) instead of an absolute/joined path, so the generated {SCRIPT} invocation stays runnable from the repo root regardless of where the project lives. Updated the corresponding tests to assert the relative path.
  • install_scripts() docstring: corrected to state that both .sh and .py scripts are made executable.

Full suite: 3860 passed, 4 skipped; ruff check clean.

Posted on behalf of @mnriem by GitHub Copilot (model: Claude Opus 4.8, autonomous).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread src/specify_cli/integrations/base.py Outdated
When neither python3 nor python is discoverable on PATH (and no project
venv is found), resolve_python_interpreter() now returns the running
interpreter (sys.executable) so the generated {SCRIPT} invocation works
in the current environment, falling back to "python3" only if that is
also unavailable. Update unit tests accordingly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +627 to +629
if script_type == "py":
interpreter = IntegrationBase.resolve_python_interpreter(project_root)
script_command = f"{interpreter} {script_command}"
Comment on lines +561 to +564
Falls back to the running interpreter (``sys.executable``) when
``PATH`` resolution fails so the generated command is guaranteed
to work in the current environment, and finally to ``"python3"``
if even that is unavailable.
For the `py` script type, the resolved interpreter may be an absolute
path containing spaces (notably `sys.executable` under Windows
`Program Files`). Quote it when it contains whitespace so the `{SCRIPT}`
invocation isn't split into multiple arguments. Add positive/negative
tests for the quoting behavior.
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.

2 participants