diff --git a/CHANGELOG b/CHANGELOG index 8b13789..d233185 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1 +1 @@ - +[#105] Add working directory option to jupyter-notebook start command. (PR: #120) diff --git a/src/commands/faas/openfaas_container_manager.py b/src/commands/faas/openfaas_container_manager.py index c14515e..ffd086a 100644 --- a/src/commands/faas/openfaas_container_manager.py +++ b/src/commands/faas/openfaas_container_manager.py @@ -1,5 +1,3 @@ -import socket - import docker from common import Container, ContainerManager @@ -12,16 +10,6 @@ def __init__(self, openfaas_container_name) -> None: container = Container(openfaas_container_name, OPENFAAS_IMAGE_NAME) super().__init__(container) - def raise_on_port_in_use(self, ports: list): - for port in ports: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - port_in_use = s.connect_ex(("localhost", port)) == 0 - - if port_in_use: - raise DockerError( - f"Port {port} is already in use. Please stop the service that is currently using this port." - ) - def start_container( self, function_version: str, diff --git a/src/commands/jupyter_notebook/jupyter_notebook_cli.py b/src/commands/jupyter_notebook/jupyter_notebook_cli.py index 31fccdf..4bf6386 100644 --- a/src/commands/jupyter_notebook/jupyter_notebook_cli.py +++ b/src/commands/jupyter_notebook/jupyter_notebook_cli.py @@ -1,12 +1,12 @@ from injector import inject -from common import Command, CommandGroup, Settings, StdoutSeverity +from common import Command, CommandGroup, Settings, StdoutSeverity, CommandOption from common.docker.exceptions import ( DockerContainerDuplicateError, DockerContainerNotFoundError, DockerError, ) - +from common.prompt_types import AbsolutePath from .jupyter_notebook_container_manager import JupyterNotebookContainerManager @@ -27,28 +27,39 @@ class JupyterNotebookStart(Command): Start a Jupyter Notebook environment. $ das-cli jupyter-notebook start + +Start a Jupyter Notebook environment with a custom working directory. + +$ das-cli jupyter-notebook start --working-dir /path/to/working/directory """ + params = [ + CommandOption( + ["--working-dir", "-w"], + help="The working directory to bind to the Jupyter Notebook container.", + required=False, + default=None, + type=AbsolutePath(), + ) + ] + @inject - def __init__(self, settings: Settings) -> None: + def __init__(self, settings: Settings, jupyter_notebook_container_manager: JupyterNotebookContainerManager) -> None: super().__init__() self._settings = settings + self._jupyter_notebook_container_manager = jupyter_notebook_container_manager - def run(self): + def run(self, working_dir: str | None = None): self._settings.raise_on_missing_file() self.stdout("Starting Jupyter Notebook...") - jupyter_notebook_container_name = self._settings.get("jupyter_notebook.container_name") jupyter_notebook_port = self._settings.get("jupyter_notebook.port") try: - jupyter_notebook_service = JupyterNotebookContainerManager( - jupyter_notebook_container_name - ) - - jupyter_notebook_service.start_container( + self._jupyter_notebook_container_manager.start_container( jupyter_notebook_port, + working_dir, ) self.stdout( f"Jupyter Notebook started on port {jupyter_notebook_port}", @@ -81,26 +92,26 @@ class JupyterNotebookStop(Command): """ @inject - def __init__(self, settings: Settings) -> None: + def __init__(self, settings: Settings, jupyter_notebook_container_manager: JupyterNotebookContainerManager) -> None: super().__init__() self._settings = settings + self._jupyter_notebook_container_manager = jupyter_notebook_container_manager def run(self): self._settings.raise_on_missing_file() self.stdout("Stopping jupyter notebook...") - jupyter_notebook_container_name = self._settings.get("jupyter_notebook.container_name") - try: - JupyterNotebookContainerManager(jupyter_notebook_container_name).stop() + self._jupyter_notebook_container_manager.stop() self.stdout( "Jupyter Notebook service stopped", severity=StdoutSeverity.SUCCESS, ) except DockerContainerNotFoundError: + container_name = self._jupyter_notebook_container_manager.get_container().get_name() self.stdout( - f"The Jupyter Notebook service named {jupyter_notebook_container_name} is already stopped.", + f"The Jupyter Notebook service named {container_name} is already stopped.", severity=StdoutSeverity.WARNING, ) @@ -119,8 +130,22 @@ class JupyterNotebookRestart(Command): Restart a Jupyter Notebook environment. $ das-cli jupyter-notebook restart + +Restart a Jupyter Notebook environment with a custom working directory. + +$ das-cli jupyter-notebook restart --working-dir /path/to/working/directory """ + params = [ + CommandOption( + ["--working-dir", "-w"], + help="The working directory to bind to the Jupyter Notebook container.", + required=False, + default=None, + type=AbsolutePath(), + ) + ] + @inject def __init__( self, @@ -131,9 +156,9 @@ def __init__( self._jupyter_notebook_start = jupyter_notebook_start self._jupyter_notebook_stop = jupyter_notebook_stop - def run(self): + def run(self, working_dir: str | None = None): self._jupyter_notebook_stop.run() - self._jupyter_notebook_start.run() + self._jupyter_notebook_start.run(working_dir) class JupyterNotebookCli(CommandGroup): @@ -157,4 +182,4 @@ def __init__( jupyter_notebook_stop.command, jupyter_notebook_restart.command, ] - ) + ) \ No newline at end of file diff --git a/src/commands/jupyter_notebook/jupyter_notebook_container_manager.py b/src/commands/jupyter_notebook/jupyter_notebook_container_manager.py index aa77882..a4ca9ce 100644 --- a/src/commands/jupyter_notebook/jupyter_notebook_container_manager.py +++ b/src/commands/jupyter_notebook/jupyter_notebook_container_manager.py @@ -1,4 +1,5 @@ import docker +import os from common import Container, ContainerManager from common.docker.exceptions import DockerError @@ -18,9 +19,17 @@ def __init__(self, jupyter_container_name) -> None: def start_container( self, port: int, + working_dir: str | None = None, ): self.raise_running_container() + volumes = { + (working_dir or os.getcwd()): { + "bind": "/home/jovyan/work", + "mode": "rw" + } + } + try: container = self._start_container( restart_policy={ @@ -30,6 +39,7 @@ def start_container( ports={ "8888/tcp": port, }, + volumes=volumes, ) return container diff --git a/src/commands/jupyter_notebook/jupyter_notebook_module.py b/src/commands/jupyter_notebook/jupyter_notebook_module.py index 56944b8..2239915 100644 --- a/src/commands/jupyter_notebook/jupyter_notebook_module.py +++ b/src/commands/jupyter_notebook/jupyter_notebook_module.py @@ -1,8 +1,25 @@ from common import Module from .jupyter_notebook_cli import JupyterNotebookCli - +from .jupyter_notebook_container_manager import JupyterNotebookContainerManager +from commands.config.config_cli import Settings class JupyterNotebookModule(Module): _instance = JupyterNotebookCli - _dependecy_injection = [] + + def __init__(self) -> None: + super().__init__() + + self._settings = Settings() + + self._dependecy_injection = [ + ( + JupyterNotebookContainerManager, + self._jupyter_notebook_container_manager_factory, + ) + ] + + + def _jupyter_notebook_container_manager_factory(self) -> JupyterNotebookContainerManager: + container_name = self._settings.get("jupyter_notebook.container_name") + return JupyterNotebookContainerManager(container_name) \ No newline at end of file diff --git a/src/common/docker/container_manager.py b/src/common/docker/container_manager.py index 930c68c..9f8fcba 100644 --- a/src/common/docker/container_manager.py +++ b/src/common/docker/container_manager.py @@ -2,6 +2,7 @@ import time from typing import Any, AnyStr, Union +import socket import docker import docker.errors @@ -76,6 +77,17 @@ def _start_container(self, **kwargs) -> Any: except docker.errors.APIError as e: raise DockerError(e.explanation) + + def raise_on_port_in_use(self, ports: list): + for port in ports: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + port_in_use = s.connect_ex(("localhost", port)) == 0 + + if port_in_use: + raise DockerError( + f"Port {port} is already in use. Please stop the service that is currently using this port." + ) + def raise_running_container(self): if self.is_running(): raise DockerContainerDuplicateError() diff --git a/src/common/prompt_types.py b/src/common/prompt_types.py index 4c92228..316ebf0 100644 --- a/src/common/prompt_types.py +++ b/src/common/prompt_types.py @@ -1,8 +1,8 @@ +import os import re from typing import Union -from click import ParamType - +from click import ParamType, Path as ClickPath from common.network import is_server_port_available @@ -49,3 +49,22 @@ def convert(self, value, param, ctx): def __repr__(self): return "ReachableIpAddress(%r, %r)" % (self.port, self.username) + +class AbsolutePath(ClickPath): + def __init__(self, *args, **kwargs): + super().__init__( + file_okay=False, + dir_okay=True, + exists=True, + writable=True, + readable=True, + path_type=str, + *args, + **kwargs + ) + + def convert(self, value, param, ctx): + path = super().convert(value, param, ctx) + if not os.path.isabs(path): + self.fail("The path must be absolute.", param, ctx) + return path