Skip to content

feat(python): add support for creating python .venv and installing dependencies#346

Merged
zimeg merged 16 commits intomainfrom
mwbrooks-python-venv
Feb 27, 2026
Merged

feat(python): add support for creating python .venv and installing dependencies#346
zimeg merged 16 commits intomainfrom
mwbrooks-python-venv

Conversation

@mwbrooks
Copy link
Member

@mwbrooks mwbrooks commented Feb 20, 2026

Changelog

Updated the slack create and slack init commands to support creating a Python virtual environment (.venv) when it doesn't exist and installing the project's dependencies from requirements.txt and pyproject.toml.

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.txt and pyproject.toml.

Out-of-scope:

  • Activating the virtual environment on each Slack CLI commands. We can follow-up in another PR with that feature.

Known issues:

  • When a pyproject.toml doesn't include a [project] section or dependencies array, 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:

image

Example of an app with an incomplete pyproject.toml:

image

Reviewers

Example of an app with a valid pyproject.toml:

$ lack create my-test-app/
# → Select "AI Agent App"
# → Select "Bolt for Python"

# Validate output:
# → Confirm: "Created virtual environment at .venv"
# → Confirm: "Installed dependencies from pyproject.toml"
# → Confirm: "Installed dependencies from requirements.txt"
# → Confirm: No errors

# Validate .env
$ ls -al my-test-app/.venv
# → Confirm: Exists

# Test app dependencies installed
$ cd my-test-app/
$ source .venv/bin/activate
$ lack run
# → Confirm: App starts

# Clean up
$ cd ../
$ rm -rf my-test-app/

Example of an app with an incomplete pyproject.toml:

$ lack create my-test-app/
# → Select "Starter App"
# → Select "Bolt for Python"

# Validate output:
# → Confirm: "Created virtual environment at .venv"
# → Confirm: "Error updating pyproject.toml: pyproject.toml missing project section"
# → Confirm: "Installed dependencies from pyproject.toml"
# → Confirm: "Installed dependencies from requirements.txt"

# Validate .env
$ ls -al my-test-app/.venv
# → Confirm: Exists

# Test app dependencies installed
$ cd my-test-app/
$ source .venv/bin/activate
$ lack run
# → Confirm: App starts

# Clean up
$ cd ../
$ rm -rf my-test-app/

Requirements

@mwbrooks mwbrooks self-assigned this Feb 20, 2026
@mwbrooks mwbrooks added enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment area:bolt-python Related to github.com/slackapi/bolt-python changelog Use on updates to be included in the release notes labels Feb 20, 2026
@mwbrooks mwbrooks added this to the Next Release milestone Feb 20, 2026
@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 71.60494% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.68%. Comparing base (44cb5f9) to head (abe12d0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/runtime/python/python.go 71.25% 14 Missing and 9 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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 mwbrooks changed the title feat(python): add support for creating and activating venv feat(python): add support for creating a python .venv Feb 20, 2026
@mwbrooks mwbrooks changed the title feat(python): add support for creating a python .venv feat(python): update 'create' and 'init' commands to support creating a python .venv Feb 20, 2026
@mwbrooks mwbrooks changed the title feat(python): update 'create' and 'init' commands to support creating a python .venv feat(python): update 'create' and 'init' to support creating a python .venv Feb 20, 2026
@mwbrooks mwbrooks changed the title feat(python): update 'create' and 'init' to support creating a python .venv feat(python): update 'create' and 'init' to support creating and installing python .venv Feb 20, 2026
@mwbrooks mwbrooks changed the title feat(python): update 'create' and 'init' to support creating and installing python .venv feat(python): add support for creating python .venv and installing dependencies Feb 20, 2026
Copy link
Member Author

@mwbrooks mwbrooks left a comment

Choose a reason for hiding this comment

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

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
Copy link
Member Author

Choose a reason for hiding this comment

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

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.
Comment on lines +98 to +108
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)
Copy link
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

@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 📚

@mwbrooks mwbrooks marked this pull request as ready for review February 21, 2026 00:33
@mwbrooks mwbrooks requested a review from a team as a code owner February 21, 2026 00:33
Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

When a pyproject.toml doesn't include a [project] section or dependencies array, the Slack CLI will display an error. It gracefully fails by continuing with the installation process for requirements.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",
Copy link
Member

Choose a reason for hiding this comment

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

🐍 praise: Such a nice diff to find!

Comment on lines -222 to -239
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")))
Copy link
Member

Choose a reason for hiding this comment

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

🪓 praise: Farewell setup steps! I notice #347 brings changes to make the activation step automatic too! Really neat!

Comment on lines +98 to +108
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)
Copy link
Member

Choose a reason for hiding this comment

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

@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")
Copy link
Member

Choose a reason for hiding this comment

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

🌟 praise: Debugging the start of something and printing the end is a super fascinating pattern!

Comment on lines +222 to +224
existingContent: `[project]
name = "my-app"
dependencies = ["pytest==8.3.2"]`,
Copy link
Member

Choose a reason for hiding this comment

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

🧪 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

Copy link
Member

Choose a reason for hiding this comment

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

👁️‍🗨️ 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
Copy link
Member

Choose a reason for hiding this comment

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

🐣 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!

@zimeg zimeg merged commit 3417188 into main Feb 27, 2026
8 checks passed
@zimeg zimeg deleted the mwbrooks-python-venv branch February 27, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:bolt-python Related to github.com/slackapi/bolt-python changelog Use on updates to be included in the release notes enhancement M-T: A feature request for new functionality semver:minor Use on pull requests to describe the release version increment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants