feat(python): auto-activate venv before hook execution#347
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.
Set VIRTUAL_ENV, prepend venv bin to PATH, and unset PYTHONHOME on the CLI process before any hooks run. This lets hook scripts (e.g. get-hooks, start, deploy) resolve the venv's Python and installed packages without requiring developers to manually activate the venv. Activation happens in InitSDKConfig after finding the project root and before reading/executing hooks. It is a no-op when no .venv exists, so non-Python projects are unaffected. Failure is non-fatal (debug log).
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.
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.
Return a boolean from ActivateVenvIfPresent to indicate whether activation occurred, and log a debug message on success.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #347 +/- ##
=======================================
Coverage 64.66% 64.67%
=======================================
Files 213 213
Lines 18018 18042 +24
=======================================
+ Hits 11652 11668 +16
- Misses 5277 5282 +5
- Partials 1089 1092 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
mwbrooks
left a comment
There was a problem hiding this comment.
Comments for those kind reviewers! 🙇🏻
internal/runtime/python/python.go
Outdated
| if err := os.Setenv("VIRTUAL_ENV", venvPath); err != nil { | ||
| return false, err | ||
| } | ||
| if err := os.Setenv("PATH", binDir+string(filepath.ListSeparator)+os.Getenv("PATH")); err != nil { | ||
| return false, err | ||
| } | ||
| os.Unsetenv("PYTHONHOME") |
There was a problem hiding this comment.
note: I imagine we should create wrappers for these calls in our slackdeps.Os 🤔
There was a problem hiding this comment.
note: I updated this PR to use slackdeps.Os and added slackdeps.Os.lookupEnv.
There was a problem hiding this comment.
@mwbrooks 🌲 So good! I think we'll want to reuse this in other places soon?
| // ActivatePythonVenvIfPresent activates a Python virtual environment if one | ||
| // exists in the given project directory. This sets VIRTUAL_ENV, prepends the | ||
| // venv bin directory to PATH, and unsets PYTHONHOME on the current process so | ||
| // that child processes (hook scripts) inherit the activated venv. | ||
| func ActivatePythonVenvIfPresent(fs afero.Fs, projectDir string) (bool, error) { | ||
| return python.ActivateVenvIfPresent(fs, projectDir) | ||
| } | ||
|
|
There was a problem hiding this comment.
note: While it feels wrong to have Python in the generic runtime this allows us to activate it for any project. We could move this into the Python runtime and think of a more general function that could be used across all runtimes.
Upside of this approach is that JS projects that include a .venv (perhaps for scripts) can be activated automatically.
| if activated, err := runtime.ActivatePythonVenvIfPresent(c.Fs, dirPath); err != nil { | ||
| c.IO.PrintDebug(ctx, "failed to activate Python virtual environment: %s", err) | ||
| } else if activated { | ||
| c.IO.PrintDebug(ctx, "Activated Python virtual environment .venv") |
There was a problem hiding this comment.
note: I'd love to display this in the regular output, but it's tough to get a nice format because it can be displayed in many different situations.
There was a problem hiding this comment.
Would love to hear your thoughts on this! IIRC logic similar to what's here exists for the get-hooks activation, but we often miss problems that should be at least reported. Even if the command continues I think!
…bility Add Unsetenv to the Os interface/implementation/mock and refactor ActivateVenvIfPresent to accept types.Os instead of calling the os package directly. This allows the test to use mocks instead of mutating the real process environment.
Adopt map-based table-driven test pattern from main while preserving new ActivateVenvIfPresent and getVenvBinDir functions and their tests. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg
left a comment
There was a problem hiding this comment.
@mwbrooks This feels awesome to use! The steps after create are much more quick now and apps feel "lighter" to run somehow... 🐍
I'm merging this after leaving a few comments that weren't blocking but might be nice to follow up on as we encounter edges in approach - thanks for bringing this to the finish 🏁 ✨
| // getVenvBinDir returns the platform-specific bin directory inside a virtual environment | ||
| func getVenvBinDir(venvPath string) string { | ||
| if runtime.GOOS == "windows" { | ||
| return filepath.Join(venvPath, "Scripts") | ||
| } | ||
| return filepath.Join(venvPath, "bin") | ||
| } |
There was a problem hiding this comment.
👁️🗨️ issue(non-blocking): I'm finding the windows option is erroring with perhaps unrelated hook errors in execution explored in slackapi/python-slack-hooks#96
| if !venvExists(fs, venvPath) { | ||
| return false, nil | ||
| } |
There was a problem hiding this comment.
🐍 question: Do we want to avoid reactivating a virtual environment if one is present? This might be an issue if different environments are activated for different reasons, but I'm unsure if that's a common practice.
I'm alright merging this without a clear answer to extend these approaches based on feedback! The happy path works so well and .venv appears to be convention without suggesting .venv.testing or similar.
| if activated, err := runtime.ActivatePythonVenvIfPresent(c.Fs, dirPath); err != nil { | ||
| c.IO.PrintDebug(ctx, "failed to activate Python virtual environment: %s", err) | ||
| } else if activated { | ||
| c.IO.PrintDebug(ctx, "Activated Python virtual environment .venv") |
There was a problem hiding this comment.
Would love to hear your thoughts on this! IIRC logic similar to what's here exists for the get-hooks activation, but we often miss problems that should be at least reported. Even if the command continues I think!
Changelog
Summary
This pull request gives Python the "create and run" experience that Node and Deno enjoy.
It enables auto-activation of venv, when it exists, so that commands can run safely and successfully even when the terminal session doesn't have a virtual environment activated. When the
.venvis missing, the CLI will continue like normal.Test coverage:
mwbrooks-python-venvis merged, this branch will report higher coverageMerge order:
mwbrooks-python-venvmwbrooks-python-venvthen merge this PR intomainPreview
2026-02-20-create-run-python.mov
Reviewers
Bolt for Python:
Bolt for JavaScript:
Requirements