Skip to content

Commit 8dad9d7

Browse files
authored
Enable installing individual packages (#37)
2 parents 6fc06f6 + d2d9ec3 commit 8dad9d7

22 files changed

+675
-184
lines changed

.github/workflows/run_tests.yml

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ permissions:
1313
contents: read
1414

1515
jobs:
16-
build:
16+
lint:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- uses: actions/checkout@v3
@@ -23,9 +23,6 @@ jobs:
2323
with:
2424
python-version: "3.10"
2525

26-
- name: Install zsh
27-
run: sudo apt-get update; sudo apt-get install -y zsh
28-
2926
- name: Install dependencies
3027
run: |
3128
python3.10 -m venv .venv
@@ -40,13 +37,52 @@ jobs:
4037
python -m black --config pyproject.toml --check .
4138
python -m mypy --config-file pyproject.toml
4239
40+
test-bash:
41+
runs-on: ubuntu-latest
42+
needs: lint
43+
steps:
44+
- uses: actions/checkout@v3
45+
46+
- name: Set up Python 3.10
47+
uses: actions/setup-python@v3
48+
with:
49+
python-version: "3.10"
50+
51+
- name: Install dependencies
52+
run: |
53+
python3.10 -m venv .venv
54+
. .venv/bin/activate
55+
python -m pip install --require-virtualenv --upgrade pip
56+
python -m pip install --require-virtualenv -r dev-requirements.txt
57+
4358
- name: Test with pytest (bash)
4459
run: |
4560
export SHELL="/usr/bin/bash"
4661
. .venv/bin/activate
4762
python -m pytest -n auto .
4863
shell: /usr/bin/bash -e {0}
4964

65+
test-zsh:
66+
runs-on: ubuntu-latest
67+
needs: lint
68+
steps:
69+
- uses: actions/checkout@v3
70+
71+
- name: Set up Python 3.10
72+
uses: actions/setup-python@v3
73+
with:
74+
python-version: "3.10"
75+
76+
- name: Install zsh
77+
run: sudo apt-get update; sudo apt-get install -y zsh
78+
79+
- name: Install dependencies
80+
run: |
81+
python3.10 -m venv .venv
82+
. .venv/bin/activate
83+
python -m pip install --require-virtualenv --upgrade pip
84+
python -m pip install --require-virtualenv -r dev-requirements.txt
85+
5086
- name: Test with pytest (zsh)
5187
run: |
5288
export SHELL="/usr/bin/zsh"

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## [v2.0.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v2.0.0)
3+
## [v2.0.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/tree/release/2.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)
@@ -34,12 +34,14 @@
3434
## [v1.4.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v1.4.0) (2023-10-30)
3535

3636
### Minor changes
37-
* `venv sync` command marked as deprecated with removal planned for `v2.0`. Use `venv install <requirements>.lock` instead. [#14](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/14)
3837
* `venv install` now runs `venv clear` before installation. This ensures that the enrivonment doesn't end up with orphaned packages after making changes to `requirements.txt`. [#9](https://github.com/SallingGroup-AI-and-ML/venv-cli/issues/9)
3938

39+
## Minor changes
40+
* `venv sync` command marked as deprecated with removal planned for `v2.0`. Use `venv install <requirements>.lock` instead. [#14](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/14)
41+
4042
## [v1.3.0](https://github.com/SallingGroup-AI-and-ML/venv-cli/releases/tag/v1.3.0) (2023-10-30)
4143

42-
### Major changes
44+
## Major changes
4345
* `venv lock` no longer tries to fill in credentials for packages installed via VCS. This behavior was undocumented and difficult to maintain and ultimately tried to alleviate a shortcoming of the way `pip` handles these credentials. [#11](https://github.com/SallingGroup-AI-and-ML/venv-cli/pull/11)
4446
For users who have credentials as part of URLs in their `requirements.txt` files, there are other ways to handle credentials, e.g. filling them in `requirements.lock` manually, using a `.netrc` file to store the credetials or using a keyring. See https://pip.pypa.io/en/stable/topics/authentication/ for more info.
4547

README.md

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,31 @@
44

55
## Overview
66
`venv-cli` is a CLI tool to help create and manage virtual python environments.
7-
It uses `pip` and `python -m venv` underneath, and so only requires core python. This alleviates the bootstrapping problem of needing to install a python package using your system `python` and `pip` before you are able to create virtual environments.
7+
It is built on `pip` and `python -m venv`, and so only requires packages that are already part of the core python installation; no third-party python packages required. This alleviates the bootstrapping problem of needing to install a python package using your system `python` and `pip` before you are able to create virtual environments.
88

99
You also don't need `conda`, `pyenv`, `pythonz` etc. to manage your python versions. Just make sure the correct version of python is installed on your system, then reference that specific version when creating the virtual environment, and everything just works. No shims, no path hacks, just the official `python` build.
1010

1111
## Installation
1212

13-
Clone this repository, then run the `install.sh` script from your favourite shell:
13+
Clone this repository, then run the `install.sh` script:
1414
```console
15-
$ bash install.sh
15+
$ ./install.sh
1616
```
1717
This will install the `venv` source file, along with an uninstall script, in `/usr/local/share/venv/`, and add a line in the appropriate shell `rc`-file (e.g. `~/.bashrc`) sourcing the `venv` source script.
1818

19-
This makes the `venv` command avaiable in your terminal. To check if it works, restart the terminal and run
19+
The default shell is `bash`. To install for a different shell, specify the shell name, e.g.
20+
```console
21+
$ ./install.sh zsh
22+
```
23+
24+
The installation makes the `venv` command available in your terminal. To check if it works, restart the terminal and run
2025
```console
2126
$ venv --version
2227
venv-cli 1.0.0
2328
```
2429

25-
The install script also adds command completions for the invoked shell.
26-
2730
# Uninstall
28-
To uninstall `venv` and remove all files, run the uninstall script at `/usr/local/share/venv/`:
31+
To uninstall `venv` and remove all files, run the uninstall script placed at `/usr/local/share/venv/`:
2932
```console
3033
$ bash /usr/local/share/venv/uninstall.sh
3134
```
@@ -54,17 +57,16 @@ $ venv create 3.9 venv-name
5457

5558
If you don't have the specific version of python installed yet, you can get it by running
5659
```console
57-
$ sudo apt install python<version>-venv
60+
$ sudo apt install python<version>
5861
```
5962
e.g.
6063
```console
61-
$ sudo apt install python3.10-venv
64+
$ sudo apt install python3.10
6265
```
63-
64-
The `-venv` part is necessary to be able to use this system python to create virtual environments.
66+
(or in the case of Debian-based distributions, like Ubuntu, `sudo apt install python3.10-venv`. The `-venv` part is necessary to be able to use the system python to create virtual environments.)
6567

6668
## Activating and deactivating the virtual environment
67-
To activate the virtual environment, from the folder containing `.venv` run
69+
To activate the virtual environment, place yourself _in the folder containing_ the `.venv` folder, then run
6870
```console
6971
$ venv activate
7072
```
@@ -74,64 +76,62 @@ To deactivate it again, run
7476
$ venv deactivate
7577
```
7678

77-
## Install packages/requirements
78-
The proper way to install packages in the virtual environment is to add them to a `requirements.txt` file and then install from that:
79-
79+
## Install/uninstall packages and requirements
80+
To install a single package, simply run
8081
```console
81-
$ echo "pandas ~= 1.5" >> requirements.txt
82+
$ venv install <package>
83+
```
8284

83-
$ venv install requirements.txt
85+
This will install the `<package>` in the current environment. However, it does more than that.
86+
A main design philosophy of `venv-cli` is to always keep the current environment in a reproducible state. For this reason, `venv-cli` aims to always keep a requirements file up to date with that state.
8487

85-
Installing requirements from requirements.txt
86-
Collecting pandas~=1.5
87-
Using cached pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.1 MB)
88-
Collecting python-dateutil>=2.8.1
89-
Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
90-
Collecting numpy>=1.21.0
91-
Using cached numpy-1.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.6 MB)
92-
Collecting pytz>=2020.1
93-
Using cached pytz-2023.3-py2.py3-none-any.whl (502 kB)
94-
Collecting six>=1.5
95-
Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
96-
Installing collected packages: pytz, six, numpy, python-dateutil, pandas
97-
Successfully installed numpy-1.25.1 pandas-1.5.3 python-dateutil-2.8.2 pytz-2023.3 six-1.16.0
98-
```
88+
This means that when running `venv install <package>`, the package is first added (or appended) to a `requirements.txt` file in the current folder, and then the command `venv install -r requirements.txt` is run, which clears the entire environment and reinstalls it from scratch using the requirements specified in `requirements.txt`.
9989

100-
In fact, if you don't specify the file name, `venv` will assume that you want to install from `requirements.txt`, so
101-
```console
102-
$ venv install
90+
Unlike `pip install <package>`, which leaves no trace, this ensures that the `requirements.txt` keeps a record of the packages that have been manually installed.
10391

104-
$ venv install requirements.txt
92+
In the same spirit, `venv uninstall <package>` first removes the package from `requirements.txt`, then runs `venv install -r requirements.txt` to reinstall the environment from scratch. Unlike `pip uninstall <package>`, this ensures that the uninstall does not leave any "orphaned" packages in the current environment (packages that were installed as secondary dependencies, but are no longer needed since the primary dependency has been uninstalled).
93+
94+
### Requirements files
95+
To specify a different requirements file to install to/uninstall from, use `-r <requirements>` :
96+
```console
97+
$ venv install numpy 'pandas >= 2.0' -r core.txt
98+
```
99+
This will add `numpy` and `pandas >= 2.0` as requirements in `core.txt`, then install from that file. Similarly,
100+
```console
101+
$ venv uninstall pandas -r core.txt
105102
```
106-
are equivalent.
103+
will remove the `pandas >= 2.0` requirement from `core.txt` again, then reinstall the environment using the updated `core.txt`.
107104

108-
The installed packages are then _locked_ into the corresponding `.lock`-file, e.g. running `venv install dev-requirements.txt` will lock those installed packages into `dev-requirements.lock`[^1].
105+
### Lock files
106+
When installing or uninstalling packages, the resulting environment is _locked_ into a corresponding `.lock`-file, e.g. running `venv install -r requirements.txt` will lock the installed packages into `requirements.lock`[^1].
109107

110-
Installing packages this way makes sure that they are tracked, since installing them with `pip install` will keep no record of which packages have been installed in the environment, making it difficult to reproduce later on.
108+
This file is useful if a reproducible install is needed, e.g. when deploying a project to a different machine, or when running a colleagues project. Where `requrements.txt` is used to specify the packages and version your project _needs_ (and nothing more), installing from `requirements.lock` makes sure that you get the exact version of every package.
111109

112-
### Development packages
113-
If you have both production and development package requirements, keep them in separate requirements-files, e.g. `requirements.txt` for production and `dev-requirements.txt` for development. An example of these could be:
110+
### Additional requirements
111+
If you have both production and development package requirements, keep them in separate requirements-files, e.g. `requirements.txt` for production requirements and `test.txt` for requirements needed when running tests. An example of these could be:
114112
```bash
115113
# requirements.txt
116114
numpy
117115
pandas ~= 1.5
118116

119117

120-
# dev-requirements.txt
118+
# test.txt
121119
-r requirements.txt
122-
jupyter
123-
matplotlib
120+
pytest
121+
pytest-cov
124122
```
125123

126-
The `-r requirements.txt` will make sure that installing development requirements also install production requirements.
124+
You can then use either
125+
```console
126+
$ venv install -r requirements.txt
127+
```
127128

128-
## Reproducing environment
129-
To install a reproducible environment, you need to install from a `.lock`-file, since those have all versions of all requirements locked[^1]:
129+
To install production requirements only, or
130130
```console
131-
$ venv install requirements.lock
131+
$ venv install -r test.txt
132132
```
133133

134-
This will first clear the environment of any installed packages, then install the packages and versions specified in `requirements.lock`.
134+
to install both production and test requirements. The `-r requirements.txt` in `test.txt` is what makes sure that installing test requirements also installs the requirements from `requirements.txt`.
135135

136136
## Clearing the environment
137137
If you want to manually clear the environment, you can run
@@ -149,19 +149,27 @@ $ venv clear
149149
$ venv install requirements.txt
150150
```
151151

152-
## Contributing
152+
## Deleting the environment
153+
To completely delete the virtual environment and everything in it, run
154+
```console
155+
$ venv delete
156+
```
153157

154-
As this is meant to be a lightweight tool providing simple, QoL improvements to working with `pip` and `python -m venv`, we will not be adding a lot of big, additional features.
158+
(this will not delete any requirement or .lock-files). This will ask for confirmation before deleting the virtual environment. To give immediate confirmation, pass the `-y` flag:
159+
```console
160+
$ venv delete -y
161+
```
155162

156-
That said, pull requests are welcome. For bigger changes, please open an issue first to discuss what you would like to change.
163+
## Contributing
164+
Before creating a pull request, please open an issue first to discuss what you would like to change.
157165

158-
To contribute, clone the repo and create a branch, create a virtual environment (preferably using `venv-cli`) and install `dev-requirements.txt`. When you are done with your changes, run the test suite with
166+
To contribute, clone the repo and create a branch, create a virtual environment and install `dev-requirements.txt`. When you are done with your changes, run the test suite with
159167
```console
160168
$ pytest .
161169
```
162170
then create a pull request for the `develop` branch.
163171

164-
Every subcommand has its own test file `tests/test_venv_<command>.py` Please make sure to add/update tests as appropriate.
172+
Every (public) subcommand has its own test file `tests/test_venv_<command>.py` Please make sure to add/update tests as appropriate.
165173

166174
### Git Flow
167175
This project follows the [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The default development branch is accordingly named `develop`, and the branch `main` is reserved for tagged releases and hotfixes. Other branches should be named according to their purpose:

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

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
# bash completion for venv -*- shell-script -*-
22

33
_venv() {
4-
local cur_word prev_word _subcommands subcommands help_options
4+
local first_word second_word cur_word prev_word _subcommands subcommands help_options
5+
first_word="${COMP_WORDS[0]}"
6+
second_word="${COMP_WORDS[1]}"
57
cur_word="${COMP_WORDS[COMP_CWORD]}"
68
prev_word="${COMP_WORDS[COMP_CWORD-1]}"
79

8-
_subcommands="activate clear create deactivate delete install lock"
10+
_subcommands="activate clear create deactivate delete install lock uninstall"
911
subcommands=( $(compgen -W "${_subcommands}" -- "${cur_word}") )
1012
help_options=( $(compgen -W "-h --help" -- "${cur_word}") )
1113

12-
# Generate completions for subcommand options
1314
compopt -o nosort
14-
case "${prev_word}" in
15-
"venv")
16-
# If only 'venv' has been entered, generate list of subcommands and options
17-
COMPREPLY+=( ${subcommands[*]} )
18-
COMPREPLY+=( ${help_options[*]} )
15+
if [ "${first_word}" != "venv" ]; then
16+
return
17+
fi
1918

20-
local version_options
21-
version_options=( $(compgen -W "-V --version" -- "${cur_word}") )
22-
COMPREPLY+=( ${version_options[*]} )
23-
;;
19+
if [ "${prev_word}" == "venv" ]; then
20+
# If only 'venv' has been entered, generate list of subcommands and options
21+
COMPREPLY+=( ${subcommands[*]} )
22+
COMPREPLY+=( ${help_options[*]} )
23+
24+
local version_options
25+
version_options=( $(compgen -W "-V --version" -- "${cur_word}") )
26+
COMPREPLY+=( ${version_options[*]} )
27+
return
28+
fi
29+
30+
# Generate completions for subcommand options
31+
case "${second_word}" in
2432
"create")
2533
# Generate list of all available python3 versions
2634

@@ -45,10 +53,30 @@ _venv() {
4553
COMPREPLY+=( $(compgen -W "-y" -- "${cur_word}") )
4654
;;
4755
"install")
48-
# Generate completions for requirement and lock file paths
49-
COMPREPLY+=( $(compgen -f -X '!(*.txt|*.lock)' -- "${cur_word}" | sort) )
50-
COMPREPLY+=( ${help_options[*]} )
51-
compopt -o plusdirs +o nosort # Add directories after generated completions
56+
case "${prev_word}" in
57+
"-r"|"--requirement")
58+
# Generate completions for requirement and lock file paths if -r or --requirement is used
59+
COMPREPLY+=( $(compgen -f -X '!(*.txt|*.lock)' -- "${cur_word}" | sort) )
60+
compopt -o plusdirs +o nosort # Add directories after generated completions
61+
;;
62+
*)
63+
COMPREPLY+=( ${help_options[*]} )
64+
COMPREPLY+=( $(compgen -W "-r --requirement -s --skip-lock --pip-args" -- "${cur_word}") )
65+
;;
66+
esac
67+
;;
68+
"uninstall")
69+
case "${prev_word}" in
70+
"-r"|"--requirement")
71+
# Generate completions for requirements file paths if -r or --requirement is used
72+
COMPREPLY+=( $(compgen -f -X '!(*.txt)' -- "${cur_word}" | sort) )
73+
compopt -o plusdirs +o nosort # Add directories after generated completions
74+
;;
75+
*)
76+
COMPREPLY+=( ${help_options[*]} )
77+
COMPREPLY+=( $(compgen -W "-r --requirement -s --skip-lock --pip-args" -- "${cur_word}") )
78+
;;
79+
esac
5280
;;
5381
"lock")
5482
# Generate completions for lock file paths

0 commit comments

Comments
 (0)