Skip to content
Merged
3 changes: 3 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
with:
python-version: '3.9'

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set go env
run: |
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
Expand Down
26 changes: 26 additions & 0 deletions internal/testutil/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package testutil

import (
"bytes"
"os"
"os/exec"

"github.com/stretchr/testify/require"
)

func RunCommand(t TestingT, name string, args ...string) {
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
require.NoError(t, cmd.Run())
}

func CaptureCommandOutput(t TestingT, name string, args ...string) string {
cmd := exec.Command(name, args...)
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
require.NoError(t, err)
return stdout.String()
}
22 changes: 22 additions & 0 deletions internal/testutil/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,25 @@ func Chdir(t TestingT, dir string) string {

return wd
}

func InsertPathEntry(t TestingT, path string) {
Comment thread
denik marked this conversation as resolved.
Outdated
var separator string
if runtime.GOOS == "windows" {
separator = ";"
} else {
separator = ":"
Comment thread
denik marked this conversation as resolved.
Outdated
}

t.Setenv("PATH", path+separator+os.Getenv("PATH"))
}

func InsertVirtualenvInPath(t TestingT, venvPath string) {
if runtime.GOOS == "windows" {
// https://github.com/pypa/virtualenv/commit/993ba1316a83b760370f5a3872b3f5ef4dd904c1
venvPath = filepath.Join(venvPath, "Scripts")
} else {
venvPath = filepath.Join(venvPath, "bin")
}

InsertPathEntry(t, venvPath)
}
17 changes: 17 additions & 0 deletions libs/python/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,32 @@ func DetectExecutable(ctx context.Context) (string, error) {
// the parent directory tree.
//
// See https://github.com/pyenv/pyenv#understanding-python-version-selection

// On Windows when virtualenv is created, the <env>/Scripts directory
// contains python.exe but no python3.exe. However, system python does have python3 entry
// and it is also added to PATH, so it is found first.

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.

Could this regress the non-venv cases where python.exe resolves to a system-wide Python 2 installation?

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.

Maybe, if they somehow managed to have Python2 installed in the first place, but why would they have that?

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.

I see python3 mentioned in another place in this module - it also won't work.

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.

Does it matter which Windows terminal is being used? Some users might use Git Bash for example and maybe the behaviour is different?

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.

that should not matter

if runtime.GOOS == "windows" {
out, err := exec.LookPath("python.exe")
Comment thread
denik marked this conversation as resolved.
Outdated
if err == nil && out != "" {
return out, nil
}
if err != nil && !errors.Is(err, exec.ErrNotFound) {
return "", err
}
}

out, err := exec.LookPath("python3")

// most of the OS'es have python3 in $PATH, but for those which don't,
// we perform the latest version lookup
if err != nil && !errors.Is(err, exec.ErrNotFound) {
return "", err
}

if out != "" {
return out, nil
}

// otherwise, detect all interpreters and pick the least that satisfies
// minimal version requirements
all, err := DetectInterpreters(ctx)
Expand Down
32 changes: 32 additions & 0 deletions libs/python/pythontest/pythontest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package pythontest

import (
"context"
"path/filepath"
"strings"

"github.com/databricks/cli/internal/testutil"
"github.com/databricks/cli/libs/python"
"github.com/stretchr/testify/require"
)

func RequirePythonVENV(t testutil.TestingT, ctx context.Context, pythonVersion string, checkVersion bool) string {
Comment thread
denik marked this conversation as resolved.
Outdated
tmpDir := t.TempDir()
testutil.Chdir(t, tmpDir)

venvName := testutil.RandomName("test-venv-")
testutil.RunCommand(t, "uv", "venv", venvName, "--python", pythonVersion, "--seed")
Comment thread
denik marked this conversation as resolved.
Outdated
testutil.InsertVirtualenvInPath(t, filepath.Join(tmpDir, venvName))

pythonExe, err := python.DetectExecutable(ctx)
require.NoError(t, err)
require.Contains(t, pythonExe, venvName)

if checkVersion {
actualVersion := testutil.CaptureCommandOutput(t, pythonExe, "--version")
expectVersion := "Python " + pythonVersion
require.True(t, strings.HasPrefix(actualVersion, expectVersion), "Running %s --version: Expected %v, got %v", pythonExe, expectVersion, actualVersion)
}

return tmpDir
}
16 changes: 16 additions & 0 deletions libs/python/pythontest/pythontest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pythontest

import (
"context"
"testing"
)

func TestVenv(t *testing.T) {
// Test at least two version to ensure we capture a case where venv version does not match system one

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.

Smart :)

for _, pythonVersion := range []string{"3.11", "3.12"} {
t.Run(pythonVersion, func(t *testing.T) {
ctx := context.Background()
RequirePythonVENV(t, ctx, pythonVersion, true)
})
}
}