feat(python): add support for creating python .venv and installing dependencies#346
feat(python): add support for creating python .venv and installing dependencies#346
Conversation
Swap pip install order so pyproject.toml is installed first to set up the project package, then requirements.txt pins take precedence as the lockfile.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #346 +/- ##
==========================================
- Coverage 64.69% 64.68% -0.01%
==========================================
Files 213 213
Lines 17967 18018 +51
==========================================
+ Hits 11623 11655 +32
- Misses 5266 5276 +10
- Partials 1078 1087 +9 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Clarify error messages in installPyProjectToml to include "updating pyproject.toml" context, and remove early return on file update errors so pip install is attempted regardless.
mwbrooks
left a comment
There was a problem hiding this comment.
Comments for the kind folks 👓
| if !exists { | ||
| err := fmt.Errorf("pyproject.toml missing project section") | ||
| return fmt.Sprintf("Error: %s", err), err | ||
| return fmt.Sprintf("Error updating pyproject.toml: %s", err), err |
There was a problem hiding this comment.
note: Improving error messages when there is an error adding slack-cli-hooks to pyproject.toml.
Route createVirtualEnvironment and runPipInstall through HookExecutor instead of os/exec, matching the npm runtime pattern. This makes the functions testable with the mock filesystem. Also add missing AddDefaultMocks call, remove stale test cases, and fix assertion strings.
| hookScript := hooks.HookScript{ | ||
| Name: "CreateVirtualEnvironment", | ||
| Command: fmt.Sprintf("%s -m venv .venv", getPythonExecutable()), | ||
| } | ||
| stdout := bytes.Buffer{} | ||
| hookExecOpts := hooks.HookExecOpts{ | ||
| Hook: hookScript, | ||
| Stdout: &stdout, | ||
| Directory: projectDirPath, | ||
| } | ||
| _, err := hookExecutor.Execute(ctx, hookExecOpts) |
There was a problem hiding this comment.
note: While it's verbose, we use the hookExecutor so that we can mock the exec calls during tests. This is how we also handle npm install.
There was a problem hiding this comment.
@mwbrooks This is a solid approach! IIRC the default hook execution protocol is used here since we don't have hooks loaded at this point, and the testing approach is great!
I'm hesitant on the CreateVirtualEnvironment hook name right now - it's almost an extension of a shared InstallDependencies to me. Rambles but this isn't exposed in configurations so for iteration ongoing 📚
zimeg
left a comment
There was a problem hiding this comment.
When a
pyproject.tomldoesn't include a[project]section or dependencies array, the Slack CLI will display an error. It gracefully fails by continuing with the installation process forrequirements.txt.
@mwbrooks Thanks for calling this out! I think the current approach is solid with your updates to sample apps that add "pyproject.toml" files - this seems sometimes recommended alongside "requirements.txt" for pinned dependencies but I'm not sure how frequent that is? I suggest we remove "requirements.txt" but we should decide later!
I'm approving now after testing well on the create experience and leaving a few notes! FWIW this adds multiple seconds to this step - 5 seconds on a solid work machine and 55 seconds on the Windows laptop I keep. The .venv path seems standard of "uv" and "pip" but that time might feel extra if "uv" benchmarks are true! 🚢 💨
This does address the extra step I often forget when switching between javascsript and python so please know these notes are rambles. Let's get this merged for next release 🎁 🐍 ✨
| "- Deno: Supported", | ||
| "- Node.js: Supported", | ||
| "- Python: Unsupported", | ||
| "- Python: Supported", |
There was a problem hiding this comment.
🐍 praise: Such a nice diff to find!
| var projectDirPathRel, _ = getProjectDirRelPath(os, os.GetExecutionDir(), projectDirPath) | ||
|
|
||
| outputs = append(outputs, fmt.Sprintf("Manually setup a %s", style.Highlight("Python virtual environment"))) | ||
| if projectDirPathRel != "." { | ||
| outputs = append(outputs, fmt.Sprintf(" Change into the project: %s", style.CommandText(fmt.Sprintf("cd %s%s", filepath.Base(projectDirPathRel), string(filepath.Separator))))) | ||
| } | ||
| outputs = append(outputs, fmt.Sprintf(" Create virtual environment: %s", style.CommandText("python3 -m venv .venv"))) | ||
| outputs = append(outputs, fmt.Sprintf(" Activate virtual environment: %s", style.CommandText(activateVirtualEnv))) | ||
|
|
||
| // Provide appropriate install command based on which file exists | ||
| if hasRequirementsTxt { | ||
| outputs = append(outputs, fmt.Sprintf(" Install project dependencies: %s", style.CommandText("pip install -r requirements.txt"))) | ||
| } | ||
| if hasPyProjectToml { | ||
| outputs = append(outputs, fmt.Sprintf(" Install project dependencies: %s", style.CommandText("pip install -e ."))) | ||
| } | ||
| // Ensure at least one dependency file exists | ||
| if !hasRequirementsTxt && !hasPyProjectToml { | ||
| err := fmt.Errorf("no Python dependency file found (requirements.txt or pyproject.toml)") | ||
| return fmt.Sprintf("Error: %s", err), err | ||
| } | ||
|
|
||
| outputs = append(outputs, fmt.Sprintf(" Learn more: %s", style.Underline("https://docs.python.org/3/tutorial/venv.html"))) |
There was a problem hiding this comment.
🪓 praise: Farewell setup steps! I notice #347 brings changes to make the activation step automatic too! Really neat!
| hookScript := hooks.HookScript{ | ||
| Name: "CreateVirtualEnvironment", | ||
| Command: fmt.Sprintf("%s -m venv .venv", getPythonExecutable()), | ||
| } | ||
| stdout := bytes.Buffer{} | ||
| hookExecOpts := hooks.HookExecOpts{ | ||
| Hook: hookScript, | ||
| Stdout: &stdout, | ||
| Directory: projectDirPath, | ||
| } | ||
| _, err := hookExecutor.Execute(ctx, hookExecOpts) |
There was a problem hiding this comment.
@mwbrooks This is a solid approach! IIRC the default hook execution protocol is used here since we don't have hooks loaded at this point, and the testing approach is great!
I'm hesitant on the CreateVirtualEnvironment hook name right now - it's almost an extension of a shared InstallDependencies to me. Rambles but this isn't exposed in configurations so for iteration ongoing 📚
| firstErr = errs[0] | ||
| // Create virtual environment if it doesn't exist | ||
| if !venvExists(fs, venvPath) { | ||
| ios.PrintDebug(ctx, "Creating Python virtual environment") |
There was a problem hiding this comment.
🌟 praise: Debugging the start of something and printing the end is a super fascinating pattern!
| existingContent: `[project] | ||
| name = "my-app" | ||
| dependencies = ["pytest==8.3.2"]`, |
There was a problem hiding this comment.
🧪 suggestion: We might want to follow up to support something as following, but I also understand that the initial update is most meaningful since this file can be modified to preference afterward:
[project.optional-dependencies]
cli = [
"slack-cli-hooks<1.0.0",
]🔗 https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#dependencies-and-requirements
There was a problem hiding this comment.
👁️🗨️ note: I do fear that'd complicate our current InstallProjectDependencies hook to require for certain cases:
$ pip install -e ".[cli]"
| return output, nil | ||
| } | ||
|
|
||
| // installRequirementsTxt handles adding slack-cli-hooks to requirements.txt |
There was a problem hiding this comment.
🐣 note: I found the test cases confusing for these "install" functions that are also updating the requirements. Not a blocker for this PR, but we might change this to be something as:
initializeAndInstallRequirementsTxt
If changes are ongoing? Apologies for calling this out in an unrelated PR too!
Changelog
Summary
This pull request adds support for creating a Python virtual environment when it doesn't already exist and installs the dependencies defined in
requirements.txtandpyproject.toml.Out-of-scope:
Known issues:
pyproject.tomldoesn't include a[project]section ordependenciesarray, the Slack CLI will display an error. It gracefully fails by continuing with the installation process for requirements.txt. However, I think we have an opportunity to update the Slack CLI to display a "Skipped updating pyproject.toml: no dependencies array" message instead of an error. That can be handled in a follow-up PR.Related PRs:
Preview
Example of an app with a valid
pyproject.toml:Example of an app with an incomplete
pyproject.toml:Reviewers
Example of an app with a valid
pyproject.toml:Example of an app with an incomplete
pyproject.toml:Requirements