Skip to content

Commit a7a2c68

Browse files
committed
Merge branch 'release/v1.5.0' into release/2.0
2 parents 19b41d9 + 01aacaf commit a7a2c68

File tree

7 files changed

+177
-13
lines changed

7 files changed

+177
-13
lines changed

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
# Changelog
22

3-
## [v2.0.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/tree/release/2.0)
3+
## [v2.0.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v2.0.0)
44

55
### Major changes
66
* `venv sync` has been removed. Use `venv install <requirements>.lock` instead. [#17](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/17)
77

8+
## [v1.5.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v1.5.0) (2024-01-04)
9+
10+
### Major changes
11+
* Added `venv delete` command. [#25](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/25)
12+
13+
Running `venv delete` completely removes the virtual environment located in the current folder.
14+
15+
### Minor changes
16+
* Added `-s` alias for `--skip-lock` when running `venv install`. [#24](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/24)
17+
18+
### Internal changes
19+
* The `run_command` test helper function can now pass through inputs to the command that is being run.
20+
21+
## [v1.4.1](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v1.4.1) (2023-10-30)
22+
23+
### Bugfixes
24+
* Fixed `venv lock` failing to lock when called as part of `venv install`. [#21](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/21)
25+
826
## [v1.4.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v1.4.0) (2023-10-30)
927

1028
### Minor changes

src/venv-cli/completions/bash/venv_completion.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ _venv() {
55
cur_word="${COMP_WORDS[COMP_CWORD]}"
66
prev_word="${COMP_WORDS[COMP_CWORD-1]}"
77

8-
_subcommands="activate clear create deactivate install lock"
8+
_subcommands="activate clear create deactivate delete install lock"
99
subcommands=( $(compgen -W "${_subcommands}" -- "${cur_word}") )
1010
help_options=( $(compgen -W "-h --help" -- "${cur_word}") )
1111

@@ -39,6 +39,11 @@ _venv() {
3939
COMPREPLY+=( ${python_versions[*]} )
4040
COMPREPLY+=( ${help_options[*]} )
4141
;;
42+
"delete")
43+
# Generate completion for help_options plus the '-y' option
44+
COMPREPLY+=( ${help_options[*]} )
45+
COMPREPLY+=( $(compgen -W "-y" -- "${cur_word}") )
46+
;;
4247
"install")
4348
# Generate completions for requirement and lock file paths
4449
COMPREPLY+=( $(compgen -f -X '!(*.txt|*.lock)' -- "${cur_word}" | sort) )

src/venv-cli/venv.sh

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ _yellow="\033[01;33m"
99
_red="\033[31m"
1010

1111
# Version number has to follow pattern "^v\d+\.\d+\.\d+.*$"
12-
_version="v1.4.0"
12+
_version="v1.5.0"
1313

1414
# Valid VCS URL environment variable pattern
1515
# https://peps.python.org/pep-0610/#specification
@@ -163,15 +163,60 @@ venv::deactivate() {
163163
}
164164

165165

166+
venv::delete() {
167+
if venv::_check_if_help_requested "$1"; then
168+
echo "venv delete [-y]"
169+
echo
170+
echo "Delete the virtual environment located in the current folder."
171+
echo "If the environment is currently active, it will be deactivated first."
172+
echo
173+
echo "Examples:"
174+
echo "$ venv delete"
175+
echo "Are you sure you want to delete the virtual environment in .venv? [y/N]"
176+
echo "y"
177+
echo "$ Virtual environment deleted!"
178+
return "${_success}"
179+
fi
180+
181+
if [ ! -d .venv ]; then
182+
venv::color_echo "${_yellow}" "No virtual environment found, nothing to delete."
183+
return "${_success}"
184+
fi
185+
186+
# If -y is not supplied as input argument, prompt the user for confirmation
187+
if [ "$1" != "-y" ]; then
188+
echo "Are you sure you want to delete the virtual environment in .venv? [y/N]"
189+
read -r response
190+
191+
local accept_pattern="^([yY][eE][sS]|[yY])$"
192+
if [[ ! "${response}" =~ $accept_pattern ]]; then
193+
venv::color_echo "${_yellow}" "Aborting."
194+
return "${_success}"
195+
fi
196+
fi
197+
198+
venv::color_echo "${_yellow}" "Deleting virtual environment in .venv ..."
199+
if [ ! -z "${VIRTUAL_ENV}" ]; then
200+
venv::deactivate
201+
fi
202+
203+
if ! rm -rf .venv; then
204+
# If the virtual environment could not be deleted
205+
return "${_fail}"
206+
fi
207+
venv::color_echo "${_green}" "Virtual environment deleted!"
208+
}
209+
210+
166211
venv::install() {
167212
if venv::_check_if_help_requested "$1"; then
168-
echo "venv install [<requirements file>] [--skip-lock] [<install args>]"
213+
echo "venv install [<requirements file>] [--skip-lock|-s] [<install args>]"
169214
echo
170215
echo "Clear the environment, then install requirements from <requirements file>,"
171216
echo "like 'requirements.txt' or 'requirements.lock'."
172217
echo "Installed packages are then locked into the corresponding .lock-file,"
173218
echo "e.g. 'venv install requirements.txt' will lock packages into 'requirements.lock'."
174-
echo "This step is skipped if '--skip-lock' is specified, or when installing"
219+
echo "This step is skipped if '--skip-lock' or '-s' is specified, or when installing"
175220
echo "directly from a .lock-file."
176221
echo
177222
echo "The <requirements file> must be in the form '*requirements.[txt|lock]'."
@@ -185,12 +230,12 @@ venv::install() {
185230
echo
186231
echo "$ venv install dev-requirements.txt"
187232
echo
188-
echo "$ venv install requirements.txt --skip-lock --no-cache"
233+
echo "$ venv install requirements.txt --skip-lock|-s --no-cache"
189234
return "${_success}"
190235
fi
191236

192237
local requirements_file
193-
if [ -z "$1" ] || [ "$1" = "--skip-lock" ]; then
238+
if [ -z "$1" ] || [ "$1" = "--skip-lock" ] || [ "$1" = "-s" ]; then
194239
# If no filename was passed
195240
requirements_file="requirements.txt"
196241

@@ -206,7 +251,7 @@ venv::install() {
206251
fi
207252

208253
local skip_lock=false
209-
if [ "$1" = "--skip-lock" ]; then
254+
if [ "$1" = "--skip-lock" ] || [ "$1" = "-s" ]; then
210255
skip_lock=true
211256
shift
212257
fi
@@ -226,7 +271,7 @@ venv::install() {
226271
return "${_success}"
227272
fi
228273

229-
venv::lock "${requirements_file}" "${lock_file}"
274+
venv::lock "${lock_file}"
230275
return "$?" # Return exit status from venv::lock command
231276
}
232277

@@ -312,6 +357,7 @@ venv::help() {
312357
echo
313358
echo "create Create a new virtual environment in the current folder"
314359
echo "activate Activate the virtual environment in the current folder"
360+
echo "delete Delete the virtual environment in the current folder"
315361
echo "install Install requirements from a requirements file in the current environment"
316362
echo "lock Lock installed requirements in a '.lock'-file"
317363
echo "clear Remove all installed packages in the current environment"
@@ -337,6 +383,7 @@ venv::main() {
337383

338384
create \
339385
| activate \
386+
| delete \
340387
| install \
341388
| lock \
342389
| clear \

tests/helpers.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from functools import wraps
55
from pathlib import Path
6-
from typing import Callable
6+
from typing import Callable, Optional
77

88
from tests.types import P, RawFilesDict, RequirementsBase, RequirementsDict
99

@@ -27,13 +27,29 @@ def _command_setup(venv_cli_path: Path, activate: bool = False) -> list[str]:
2727
return [*bash, full_command]
2828

2929

30-
def run_command(commands: str | list[str], cwd: Path = Path.cwd(), activated: bool = False) -> None:
30+
def run_command(
31+
commands: str | list[str],
32+
cwd: Path = Path.cwd(),
33+
activated: bool = False,
34+
command_input: Optional[str] = None,
35+
) -> None:
36+
"""Run a command in a subprocess, optionally activating the virtual environment first
37+
38+
Args:
39+
commands: The command(s) to run.
40+
cwd: The directory to run the command in. Defaults to the current working directory.
41+
activated: Whether to activate the virtual environment before running the command.
42+
command_input: The input to pass to the command. Defaults to None.
43+
44+
Raises:
45+
subprocess.CalledProcessError: If the command returns a non-zero exit code.
46+
"""
3147
input_commands = [commands] if isinstance(commands, str) else commands
3248

3349
setup_commands = _command_setup(venv_cli_path=_venv_cli_path, activate=activated)
3450
all_commands = [*setup_commands[:-1], setup_commands[-1] + "; ".join(input_commands)]
3551

36-
result = subprocess.run(all_commands, cwd=cwd)
52+
result = subprocess.run(all_commands, cwd=cwd, input=command_input, text=True)
3753
result.check_returncode()
3854

3955

tests/test_venv_delete.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
import pytest_cases
5+
6+
from tests.helpers import run_command
7+
8+
9+
@pytest.mark.order(after="test_venv_activate.py::test_venv_activate")
10+
@pytest_cases.parametrize("yes", ["y", "yes", "Y", "Yes", "YES"])
11+
@pytest_cases.parametrize("activated", [False, True])
12+
def test_venv_delete_ask_confirmation_yes(activated: bool, yes: str, tmp_path: Path):
13+
venv_path = tmp_path / ".venv"
14+
15+
# Execute: Create, then delete, the virtual environment
16+
run_command("venv delete", command_input=yes, cwd=tmp_path, activated=activated)
17+
18+
# Verify: check that the virtual environment directory no longer exists
19+
assert not venv_path.is_dir()
20+
21+
# Test that trying to delete again doesn't cause an error
22+
run_command("venv delete", command_input=yes, cwd=tmp_path, activated=activated)
23+
24+
25+
@pytest.mark.order(after="test_venv_activate.py::test_venv_activate")
26+
@pytest_cases.parametrize("no", ["", "n", "N", "No", "NO", "asd"])
27+
@pytest_cases.parametrize("activated", [False, True])
28+
def test_venv_delete_ask_confirmation_no(activated: bool, no: str, tmp_path: Path):
29+
venv_path = tmp_path / ".venv"
30+
31+
# Execute: Create, then try to delete, the virtual environment, but say no
32+
run_command("venv delete", command_input=no, cwd=tmp_path, activated=activated)
33+
34+
if activated:
35+
# Verify: check that the virtual environment directory still exists (if it was created)
36+
assert venv_path.is_dir()
37+
38+
39+
@pytest.mark.order(after="test_venv_activate.py::test_venv_activate")
40+
@pytest_cases.parametrize("activated", [False, True])
41+
def test_venv_delete_no_confirmation(activated: bool, tmp_path: Path):
42+
venv_path = tmp_path / ".venv"
43+
44+
# Execute: Create, then delete, the virtual environment
45+
run_command("venv delete -y", cwd=tmp_path, activated=activated)
46+
47+
# Verify: check that the virtual environment directory no longer exists
48+
assert not venv_path.is_dir()
49+
50+
# Test that trying to delete again doesn't cause an error
51+
run_command("venv delete -y", cwd=tmp_path, activated=activated)

tests/test_venv_install.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pytest_cases import parametrize_with_cases
77

88
from tests.helpers import run_command, write_files
9-
from tests.test_venv_install_cases import CasesVenvInstallRequirementstxt
9+
from tests.test_venv_install_cases import CasesVenvInstallRequirementstxt, CasesVenvInstallWithLock
1010
from tests.types import RequirementsBase, RequirementsDict
1111

1212
_package_name_regex = re.compile(r"^([a-zA-Z0-9_-]+)\b")
@@ -60,3 +60,19 @@ def test_venv_install_requirements(
6060
continue
6161

6262
_check_package_was_installed(requirement=requirement, installed_line=installed_line)
63+
64+
65+
@pytest.mark.order(after="test_venv_activate.py::test_venv_activate")
66+
@parametrize_with_cases(argnames=["files", "requirements_base"], cases=CasesVenvInstallWithLock)
67+
def test_venv_install_with_lock(
68+
files: RequirementsDict,
69+
requirements_base: RequirementsBase,
70+
tmp_path: Path,
71+
):
72+
write_files(files=files, dir=tmp_path)
73+
74+
run_command(
75+
f"venv install {requirements_base}.txt",
76+
cwd=tmp_path,
77+
activated=True,
78+
)

tests/test_venv_install_cases.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,14 @@ def case_git_dev(self) -> tuple[RawFilesDict, RequirementsBase]:
5454
"dev-requirements.txt": dev_requirements_txt,
5555
}
5656
return files, RequirementsBase.dev_requirements
57+
58+
59+
class CasesVenvInstallWithLock:
60+
@collect_requirements
61+
def case_pypi(self) -> tuple[RawFilesDict, RequirementsBase]:
62+
requirements_txt = [
63+
"python-json-logger==2.0.7",
64+
]
65+
66+
files = {"requirements.txt": requirements_txt}
67+
return files, RequirementsBase.requirements

0 commit comments

Comments
 (0)