Skip to content

Commit

Permalink
Fix the issue that virtual environments on extra tools based are tota…
Browse files Browse the repository at this point in the history
…lly not recognized
  • Loading branch information
vladpunko committed Nov 21, 2021
1 parent b567dc7 commit bc15004
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 16 deletions.
24 changes: 16 additions & 8 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,38 @@ Manage python virtual environments on the working notebook server.

## Installation

It is recommended to use this package together with [virtualenv](https://github.com/pypa/virtualenv/) and [virtualwrapper](https://bitbucket.org/virtualenvwrapper/virtualenvwrapper/) to work with python virtual environments more suitable.
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install notebook-environments on the current working machine:
It is recommended to use this package together with [virtualenv](https://github.com/pypa/virtualenv) and [virtualenvwrapper](https://bitbucket.org/virtualenvwrapper/virtualenvwrapper) to work with python virtual environments more suitable. Make sure the installed [python](https://wiki.archlinux.org/title/python) interpreters work without errors on the current operating system. To install this package as a standalone application with the command-line interface you are to run the following command:

```bash
python3 -m pip install --user notebook-environments
sudo sh -c "$(curl https://raw.githubusercontent.com/vladpunko/notebook-environments/master/install.sh)"
```

You can also install this python package on your working machine (works for Unix-like operating systems) from source code to `/usr/local/bin` as the standard system location for user's programs (this location can be changed at the user's discretion):
Use the package manager [pip](https://pip.pypa.io/en/stable) to install notebook-environments without the command-line interface:

```bash
python3 -m pip install notebook-environments
```

You can also install this python package on your working machine (works for unix-like operating systems) from source code to `/usr/local/bin` as the standard system location for user's programs (this location can be changed at the user's discretion):

```bash
# Step -- 1.
git clone --branch master https://github.com/vladpunko/notebook-environments.git
git clone --depth=1 --branch=master https://github.com/vladpunko/notebook-environments.git

# Step -- 2.
cd ./notebook-environments/

# Step -- 3.
sudo install -m 755 n.sh /usr/local/bin/n

# Step -- 4.
sudo install -m 755 notebook_environments.py /usr/local/bin/notebook-environments
```

## Basic usage

Using this program allows you to run one instance of [jupyter notebook](https://github.com/jupyter/notebook/) on your working machine and add different python virtual environments as needed.
It protects you from the trouble of installing jupyter packages in a new environment and running multiple servers.
Using this program allows you to run one instance of [notebook](https://github.com/jupyter/notebook) server on your working machine and add different python virtual environments as needed.
It protects you from the trouble of installing notebook packages in a new environment and running multiple servers.

```bash
# Step -- 1.
Expand Down Expand Up @@ -62,4 +70,4 @@ tox && tox -e lint

## License

[MIT](https://choosealicense.com/licenses/mit/)
[MIT](https://choosealicense.com/licenses/mit)
32 changes: 32 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env sh

# Copyright 2020 (c) Vladislav Punko <[email protected]>

set -o errexit

# Exit if this script is run as the superuser.
if [ "$(id -u)" -ne 0 ]; then
echo 'This program needs to be run as a superuser on the current operating system.' >&2
# Stop this program runtime and return the exit status code.
exit 13
fi

workdir="$(mktemp -d)"

# Step -- 1.
git clone --depth=1 --branch=master -- https://github.com/vladpunko/notebook-environments.git "${workdir}" > /dev/null 2>&1 || {
echo 'An error occurred while downloading the source code to the current machine.' >&2
# Stop this program runtime and return the exit status code.
exit 70
}

# Step -- 2.
install -m 755 "${workdir}/n.sh" /usr/local/bin/n

# Step -- 3.
install -m 755 "${workdir}/notebook_environments.py" /usr/local/bin/notebook-environments

_cleanup() {
rm -r -- "${workdir}"
}
trap _cleanup EXIT
31 changes: 24 additions & 7 deletions notebook_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
import subprocess
import sys

try:
_to_unicode = unicode
except NameError:
_to_unicode = str

try:
from json.decoder import JSONDecodeError
except ImportError:
Expand Down Expand Up @@ -110,12 +115,18 @@
"show_kernels",
)

__version__ = "0.8.5"
__version__ = "0.8.6"


def _in_virtual_environment():
is_using_venv = (
# Take into consideration user's virtual environments based on standard python packages.
# See information: https://www.python.org/dev/peps/pep-0405
hasattr(sys, "real_prefix") or getattr(sys, "base_prefix", sys.prefix) != sys.prefix
) # pep 405

# Check a virtual environment of the working python interpreter at this program runtime.
return hasattr(sys, "real_prefix") or getattr(sys, "base_prefix", sys.prefix) != sys.prefix
return bool(os.getenv("CONDA_PREFIX") or os.getenv("VIRTUAL_ENV") or is_using_venv)


def _get_data_path(*subdirs):
Expand Down Expand Up @@ -180,9 +191,9 @@ def _write_kernel_specification(path):
}

try:
with io.open(os.path.join(path, "kernel.json"), mode="wt") as stream_out:
# Create a new specification on the current machine.
json.dump(kernel_spec, stream_out, indent=2)
with io.open(os.path.join(path, "kernel.json"), mode="wt", encoding="utf-8") as stream_out:
# Create a new kernel specification on the current machine.
stream_out.write(_to_unicode(json.dumps(kernel_spec, ensure_ascii=False, indent=2)))
except (IOError, OSError):
_logger.error("It's impossible to create a new specification on the current machine.")
# Stop this program runtime and return the exit status code.
Expand All @@ -198,7 +209,11 @@ def _provide_required_packages():
stdout=devnull,
)
except subprocess.CalledProcessError:
_logger.error("It's impossible to install packages on the current machine.")
_logger.error((
"It's impossible to install packages on the current machine.\n"
"You are to update setup tools and run the installation process another time.\n"
"python -m pip install --upgrade pip setuptools wheel"
))
# Stop this program runtime and return the exit status code.
sys.exit(70)

Expand Down Expand Up @@ -311,7 +326,8 @@ def initialize_new_notebook_environment():
try:
from jupyter_core.paths import jupyter_path
except ImportError:
jupyter_path = _get_data_path
def jupyter_path(path): # this function is to return a list
return [_get_data_path(path)]

# Find and remove all python kernels from the working notebook server.
for path in jupyter_path("kernels"):
Expand Down Expand Up @@ -339,6 +355,7 @@ def main(): # pragma: no cover
parser = argparse.ArgumentParser(
description="Manage python virtual environments on the working notebook server."
)
parser.add_argument("-v", "--version", action="version", version=str(__version__))

# Prevent the users from using two or more arguments at this program runtime.
group = parser.add_mutually_exclusive_group(required=True)
Expand Down
35 changes: 34 additions & 1 deletion notebook_environments_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,21 @@ def tearDown(self):
# Remove all the fake python kernels after the each test case from the current machine.
self.fs.remove_object(self.data_path)

@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_virtual_environment_active(self, sys_mock):
sys_mock.activate()

# Check the current state of an active virtual environment for the working python.
self.assertTrue(notebook_environments._in_virtual_environment())

@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_virtual_environment_active_from_variables(self, sys_mock):
sys_mock.deactivate()

# Check the current state of an active virtual environment for the working python.
self.assertTrue(notebook_environments._in_virtual_environment())

@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_virtual_environment_not_active(self, sys_mock):
sys_mock.deactivate()

Expand All @@ -100,6 +109,7 @@ def test_virtual_environment_not_active(self, sys_mock):
@mock.patch("notebook_environments.platform")
@mock.patch("notebook_environments.os.path.expanduser")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_get_data_path(self, logger_mock, expanduser_mock, platform_mock, sys_mock):
sys_mock.activate()

Expand Down Expand Up @@ -131,13 +141,15 @@ def test_get_data_path(self, logger_mock, expanduser_mock, platform_mock, sys_mo
# Check the received exit status code from the function under test.
self.assertEqual(system_exit.exception.code, 1)

@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_get_kernel_name(self, sys_mock):
sys_mock.activate()

self.assertEqual(notebook_environments._get_kernel_name(), ".test")

@mock.patch("notebook_environments.os.path.basename")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_get_kernel_name_error(self, logger_mock, basename_mock, sys_mock):
sys_mock.deactivate()

Expand Down Expand Up @@ -175,6 +187,7 @@ def test_get_kernel_name_error(self, logger_mock, basename_mock, sys_mock):
# Check the received exit status code from the function under test.
self.assertEqual(system_exit.exception.code, 78)

@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_list_kernels_in(self, sys_mock):
sys_mock.deactivate()

Expand All @@ -189,6 +202,7 @@ def test_list_kernels_in(self, sys_mock):

@mock.patch("notebook_environments.os.listdir")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_list_kernels_in_error(self, logger_mock, listdir_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -215,6 +229,7 @@ def test_list_kernels_in_error(self, logger_mock, listdir_mock, sys_mock):
list(notebook_environments._list_kernels_in(""))

@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_write_python_logos(self, logger_mock, sys_mock):
sys_mock.deactivate()

Expand Down Expand Up @@ -242,6 +257,7 @@ def test_write_python_logos(self, logger_mock, sys_mock):

@mock.patch("notebook_environments.subprocess.check_call")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_provide_required_packages(self, logger_mock, check_call_mock, sys_mock):
sys_mock.activate()

Expand All @@ -263,13 +279,16 @@ def test_provide_required_packages(self, logger_mock, check_call_mock, sys_mock)

# Check the received error message that was sent from the function under test.
logger_mock.error.assert_called_with(
"It's impossible to install packages on the current machine."
"It's impossible to install packages on the current machine.\n"
"You are to update setup tools and run the installation process another time.\n"
"python -m pip install --upgrade pip setuptools wheel"
)

# Check the received exit status code from the function under test.
self.assertEqual(system_exit.exception.code, 70)

@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_write_kernel_specification(self, logger_mock, sys_mock):
sys_mock.activate()

Expand Down Expand Up @@ -314,6 +333,7 @@ def test_write_kernel_specification(self, logger_mock, sys_mock):

@mock.patch("notebook_environments.os.makedirs")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_create_dir(self, logger_mock, makedirs_mock, sys_mock):
sys_mock.deactivate()

Expand Down Expand Up @@ -346,6 +366,7 @@ def test_create_dir(self, logger_mock, makedirs_mock, sys_mock):

@mock.patch("notebook_environments.shutil.rmtree")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_remove_dir(self, logger_mock, rmtree_mock, sys_mock):
sys_mock.deactivate()

Expand Down Expand Up @@ -381,6 +402,7 @@ def test_remove_dir(self, logger_mock, rmtree_mock, sys_mock):
@mock.patch("notebook_environments._provide_required_packages")
@mock.patch("notebook_environments._write_kernel_specification")
@mock.patch("notebook_environments._write_python_logos")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_create_new_kernel(
self,
write_python_logos_mock,
Expand All @@ -400,6 +422,7 @@ def test_create_new_kernel(
self.assertTrue(write_python_logos_mock.called)

@mock.patch("notebook_environments._create_new_kernel")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_add_active_environment(self, create_new_kernel_mock, sys_mock):
sys_mock.activate()

Expand All @@ -411,6 +434,7 @@ def test_add_active_environment(self, create_new_kernel_mock, sys_mock):

@mock.patch("notebook_environments._create_new_kernel")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_add_not_active_environment(self, logger_mock, create_new_kernel_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -430,6 +454,7 @@ def test_add_not_active_environment(self, logger_mock, create_new_kernel_mock, s
self.assertEqual(system_exit.exception.code, 1)

@mock.patch("notebook_environments._get_data_path")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_remove_active_environment(self, get_data_path_mock, sys_mock):
sys_mock.activate()

Expand All @@ -442,6 +467,7 @@ def test_remove_active_environment(self, get_data_path_mock, sys_mock):

@mock.patch("notebook_environments._remove_dir")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_remove_not_active_environment(self, logger_mock, remove_dir_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -461,6 +487,7 @@ def test_remove_not_active_environment(self, logger_mock, remove_dir_mock, sys_m
self.assertEqual(system_exit.exception.code, 1)

@mock.patch("notebook_environments._get_data_path")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_remove_dead_kernels(self, get_data_path_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -477,6 +504,7 @@ def test_remove_dead_kernels(self, get_data_path_mock, sys_mock):

@mock.patch("notebook_environments._list_kernels_in")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_remove_dead_kernels_error(self, logger_mock, list_kernels_in_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -496,6 +524,7 @@ def test_remove_dead_kernels_error(self, logger_mock, list_kernels_in_mock, sys_
self.assertEqual(system_exit.exception.code, 71)

@mock.patch("notebook_environments._remove_dir")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_check_and_remove_broken_kernel(self, remove_dir_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -511,6 +540,7 @@ def test_check_and_remove_broken_kernel(self, remove_dir_mock, sys_mock):

@mock.patch("notebook_environments.print")
@mock.patch("notebook_environments._get_data_path")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_show_kernels(self, get_data_path_mock, print_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -527,6 +557,7 @@ def test_show_kernels(self, get_data_path_mock, print_mock, sys_mock):

@mock.patch("notebook_environments._list_kernels_in")
@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_show_kernels_error(self, logger_mock, list_kernels_in_mock, sys_mock):
sys_mock.deactivate()

Expand All @@ -547,6 +578,7 @@ def test_show_kernels_error(self, logger_mock, list_kernels_in_mock, sys_mock):

@mock.patch("notebook_environments._create_new_kernel")
@mock.patch("notebook_environments._get_data_path")
@mock.patch.dict("notebook_environments.os.environ", {}, clear=True)
def test_initialize_new_notebook_environment(
self,
get_data_path_mock,
Expand All @@ -564,6 +596,7 @@ def test_initialize_new_notebook_environment(
create_new_kernel_mock.assert_called_with("python3")

@mock.patch("notebook_environments._logger")
@mock.patch.dict("notebook_environments.os.environ", {"VIRTUAL_ENV": "test"}, clear=True)
def test_initialize_new_notebook_environment_error(self, logger_mock, sys_mock):
sys_mock.activate()

Expand Down

0 comments on commit bc15004

Please sign in to comment.