Repository: JanDeDobbeleer/oh-my-posh
Pull request #4400 (2023-10-29 04:32:39)

feat(python): Use parent directory as name if venv name is "venv" or ".venv"
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [x] The commit message follows the [conventional commits][cc] guidelines.
- [x] Tests for the changes have been added (for bug fixes / features).
- [x] Docs have been added/updated (for bug fixes / features).

### Description

It is somewhat common to create Python virtualenvs in the working directory of the Python project. Usually people simply create these virtualenvs with the name "venv" or ".venv". This is common enough that it's referenced in the official Python documentation: https://docs.python.org/3/library/venv.html

This change makes it so that if the base name of the determined virtualenv path is "venv" or ".venv", it will instead use the parent directory as the virtualenv's name.

ex. if your venv is in `/path/to/folder/.venv`, the value of `{{ .Venv }}` will be `folder` instead of `.venv`.

### How

Updated `src/segments/python.go` with a check in `loadContext()` to use the parent directory of the venv file if the initially calculated name is `venv` or `.venv`.
Updated python.mdx to note this behavior. 
Added a new test to `src/segments/python_test.go` for this new behavior.

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/src/go.mod b/src/go.mod
index 5676f8fb..13a1fa5c 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -89,6 +89,7 @@ require (
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	github.com/zclconf/go-cty v1.13.1 // indirect
+	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
 	golang.org/x/sync v0.4.0 // indirect
 	golang.org/x/term v0.13.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
diff --git a/src/go.sum b/src/go.sum
index cfe1f616..2716f51e 100644
--- a/src/go.sum
+++ b/src/go.sum
@@ -203,8 +203,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
-golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
 golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
 golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
diff --git a/src/segments/python.go b/src/segments/python.go
index cc1ec1fc..ba33a2ca 100644
--- a/src/segments/python.go
+++ b/src/segments/python.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
@@ -72,13 +73,26 @@ func (p *Python) loadContext() {
 		"CONDA_ENV_PATH",
 		"CONDA_DEFAULT_ENV",
 	}
+
+	defaultVenvNames := []string{
+		".venv",
+		"venv",
+	}
+
 	var venv string
 	for _, venvVar := range venvVars {
 		venv = p.language.env.Getenv(venvVar)
 		if len(venv) == 0 {
 			continue
 		}
+
 		name := platform.Base(p.language.env, venv)
+
+		if slices.Contains(defaultVenvNames, name) {
+			venv = strings.TrimSuffix(venv, name)
+			name = platform.Base(p.language.env, venv)
+		}
+
 		if p.canUseVenvName(name) {
 			p.Venv = name
 			break
diff --git a/website/docs/segments/python.mdx b/website/docs/segments/python.mdx
index 7dc24a4e..9ea5d000 100644
--- a/website/docs/segments/python.mdx
+++ b/website/docs/segments/python.mdx
@@ -9,6 +9,8 @@ sidebar_label: Python
 Display the currently active python version and virtualenv.
 Supports conda, virtualenv and pyenv (if python points to pyenv shim).
 
+If your virtual environment is a directory named `venv` or `.venv`, the virtual environment's name will be the parent directory.
+
 ## Sample Configuration
 
 import Config from '@site/src/components/Config.js';


Test patch:
diff --git a/src/segments/python_test.go b/src/segments/python_test.go
index a58fee12..e243fad1 100644
--- a/src/segments/python_test.go
+++ b/src/segments/python_test.go
@@ -149,3 +149,29 @@ func TestPythonPythonInContext(t *testing.T) {
 		assert.Equal(t, tc.Expected, python.inContext())
 	}
 }
+
+func TestPythonVirtualEnvIgnoreDefaultVenvNames(t *testing.T) {
+	cases := []struct {
+		VirtualEnvName string
+	}{
+		{VirtualEnvName: "/path/to/folder/.venv"},
+		{VirtualEnvName: "/path/to/folder/venv"},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("GOOS").Return("")
+		env.On("PathSeparator").Return("/")
+		env.On("CommandPath", mock2.Anything).Return("")
+		env.On("HasFilesInDir", mock2.Anything, "pyvenv.cfg").Return(false)
+		env.On("Getenv", "VIRTUAL_ENV").Return(tc.VirtualEnvName)
+		env.On("Getenv", "CONDA_ENV_PATH").Return("")
+		env.On("Getenv", "CONDA_DEFAULT_ENV").Return("")
+		env.On("Getenv", "PYENV_VERSION").Return("")
+		env.On("HasParentFilePath", ".python-version").Return(&platform.FileInfo{}, errors.New("no match at root level"))
+		python := &Python{}
+		python.Init(properties.Map{}, env)
+		python.loadContext()
+		assert.Equal(t, "folder", python.Venv)
+	}
+}