@@ -386,6 +386,155 @@ venv::_add_packages_to_requirements() {
386386 LC_COLLATE=C sort --ignore-case --stable -o " ${requirements_file} " " ${requirements_file} "
387387}
388388
389+ venv::uninstall () {
390+ if venv::_check_if_help_requested " $1 " ; then
391+ echo " venv uninstall <package> [...] [OPTIONS]"
392+ echo
393+ echo " Remove one or more installed packages from a requirements file, then reinstall the environment from that requirements file."
394+ echo " The resulting environment will be locked into a .lock-file."
395+ echo
396+ echo " The packages to uninsall must be the canonical package names as specified in the requirements file, e.g. 'numpy' or 'scikit-learn'."
397+ echo " If specified, the requirements file must have file extension '.txt'."
398+ echo " If no requirements file is passed, the command will assume the file 'requirements.txt' is to be used, and will fail if this file cannot be found."
399+ echo
400+ echo " Options:"
401+ echo " -h, --help Show this help and exit."
402+ echo " -r, --requirement <requirements file> Remove the package(s) from the given requirements file, then reinstall the environment."
403+ echo " If not specified, will default to using 'requirements.txt' and will fail if this file does not exist."
404+ echo " -s, --skip-lock Skip locking packages to a .lock-file after reinstallation."
405+ echo " --pip-args <ARGS> Additional arguments to pass through to pip install on reinstallation."
406+ echo
407+ echo " Examples:"
408+ echo " $ venv uninstall numpy"
409+ echo " $ venv uninstall numpy -r requirements.txt"
410+ echo " This will remove the 'numpy' requirement from 'requirements.txt', then reinstall the environment from 'requirements.txt' and lock them into 'requirements.lock'."
411+ echo
412+ echo " $ venv uninstall numpy pandas -r requirements/dev-requirements.txt -s --pip-args='--no-cache --pre'"
413+ echo " This will remove 'numpy' and 'pandas' requirements from 'requirements/dev-requirements.txt', then reinstall"
414+ echo " all requirements from 'dev-requirements.txt' without locking them."
415+ echo " The arguments '--no-cache' and '--pre' are passed on to 'pip install' on reinstallation."
416+ return " ${_success} "
417+ fi
418+
419+ # Parse arguments. Fail if invalid arguments are passed
420+ local TEMP=$( getopt -o ' r:s' --long ' requirement:,skip-lock,pip-args::' -n ' venv uninstall' -- " $@ " )
421+ local _exit=" $? "
422+ if [ " ${_exit} " -ne 0 ]; then
423+ return " ${_exit} "
424+ fi
425+
426+ local package_names=() # List of packages to uninstall
427+ local requirements_file=" "
428+ local skip_lock=" "
429+ local pip_args=" "
430+
431+ eval set -- " $TEMP " # Unpack the arguments in $TEMP into the positional parameters #1, #2, ...
432+ unset TEMP
433+
434+ # Parse arguments
435+ while true ; do
436+ # -- marks the end of the options, and anything after it is treated as a positional argument.
437+ # If "$*" = "--", there are no optional parameters left, and we can break the loop
438+ if [ " $* " = " --" ]; then
439+ shift
440+ break
441+ fi
442+
443+ case " $1 " in
444+ " -r" | " --requirement" )
445+ requirements_file=" $2 "
446+ shift 2
447+ ;;
448+ " -s" | " --skip-lock" )
449+ skip_lock=" --skip-lock"
450+ shift
451+ ;;
452+ " --pip-args" )
453+ pip_args=" $2 "
454+ shift 2
455+ ;;
456+ --)
457+ # -- marks the end of the options, and anything after it is treated as a positional argument.
458+ # For venv uninstall, positional arguments are package names
459+ shift
460+ package_names+=( " $@ " )
461+ break
462+ ;;
463+ * )
464+ if [ -z " $1 " ]; then
465+ break
466+ fi
467+ ;;
468+ esac
469+ done
470+
471+ # Fail if no package names were specified
472+ if [ " ${# package_names[@]} " -eq 0 ]; then
473+ venv::raise " No packages specified, nothing to uninstall. If you want to uninstall everything, use 'venv clear'."
474+ return " ${_fail} "
475+ fi
476+
477+ # Check the specified requirements file
478+ if [ -z " ${requirements_file} " ]; then
479+ requirements_file=" requirements.txt"
480+ venv::color_echo " ${_yellow} " " No requirements file specified, using requirements.txt"
481+ fi
482+ if ! venv::_check_install_requirements_file " ${requirements_file} " ; then
483+ # Fail if file name doesn't match required format
484+ return " ${_fail} "
485+ fi
486+
487+ # Make a temporary backup of the requirements file, in case something fails
488+ venv::_create_backup_file " ${requirements_file} "
489+
490+ # Remove package names from requirements file
491+ if ! venv::_remove_packages_from_requirements " ${requirements_file} " " ${package_names[@]} " ; then
492+ return " ${_fail} "
493+ fi
494+ if [ " $? " -eq 2 ]; then
495+ # If none of the packages were found in the requirements file, return without reinstalling
496+ venv::_remove_backup_file " ${requirements_file} "
497+ return " ${_success} "
498+ fi
499+
500+ # Remove the backup file if everything went well
501+ venv::_remove_backup_file " ${requirements_file} "
502+
503+ # Reinstall the environment from the requirements file. Pass through additional arguments
504+ venv::color_echo " ${_green} " " Reinstalling requirements from ${requirements_file} "
505+ if ! venv::install -r " ${requirements_file} " ${skip_lock} --pip-args=" ${pip_args} " ; then
506+ return " ${_fail} "
507+ fi
508+ }
509+
510+ venv::_remove_packages_from_requirements () {
511+ local requirements_file=" $1 "
512+ local package_names=(" ${@: 2} " )
513+
514+ local package_name
515+ local packages_removed=0
516+ for package_name in " ${package_names[@]} " ; do
517+ # Check if the package is in the requirements file
518+ if ! command grep -q " ^${package_name} " " ${requirements_file} " ; then
519+ venv::color_echo " ${_yellow} " " Package '${package_name} ' not found in ${requirements_file} , skipping"
520+ continue
521+ fi
522+ # Remove the package from the requirements file
523+ sed -i " /^${package_name} /d" " ${requirements_file} "
524+ packages_removed=$(( packages_removed+ 1 ))
525+ echo " Removed '${package_name} ' from ${requirements_file} "
526+ done
527+
528+ if [ " ${packages_removed} " -eq 0 ]; then
529+ venv::color_echo " ${_yellow} " " None of the specified packages found in ${requirements_file} , nothing to remove"
530+ return 2
531+ fi
532+
533+ # Sort requirements file after removing packages. LC_COLLATE is set to C to ensure lines beginning with '-'
534+ # are sorted first, instad of being ignored
535+ LC_COLLATE=C sort --ignore-case --stable -o " ${requirements_file} " " ${requirements_file} "
536+ }
537+
389538venv::_create_backup_file () {
390539 local file=" $1 "
391540 if [ -f " ${file} " ]; then
@@ -488,7 +637,8 @@ venv::help() {
488637 echo " create Create a new virtual environment in the current folder"
489638 echo " activate Activate the virtual environment in the current folder"
490639 echo " delete Delete the virtual environment in the current folder"
491- echo " install Install requirements from a requirements file in the current environment"
640+ echo " install Install individual packages, or requirements from a requirements file, in the current environment"
641+ echo " uninstall Uninstall packages from the current environment and reinstall from a requirements file"
492642 echo " lock Lock installed requirements in a '.lock'-file"
493643 echo " clear Remove all installed packages in the current environment"
494644 echo " deactivate Deactivate the currently activated virtual environment"
@@ -511,13 +661,14 @@ venv::main() {
511661 venv::_version
512662 ;;
513663
514- create \
515- | activate \
664+ activate \
665+ | clear \
666+ | create \
667+ | deactivate \
516668 | delete \
517669 | install \
518670 | lock \
519- | clear \
520- | deactivate \
671+ | uninstall \
521672 )
522673 shift
523674 venv::" ${subcommand} " " $@ "
0 commit comments