Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#105] Start Jupyter Notebook with working directory #120

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions src/commands/faas/openfaas_container_manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import socket

import docker

from common import Container, ContainerManager
Expand All @@ -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,
Expand Down
61 changes: 43 additions & 18 deletions src/commands/jupyter_notebook/jupyter_notebook_cli.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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}",
Expand Down Expand Up @@ -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,
)

Expand All @@ -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,
Expand All @@ -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):
Expand All @@ -157,4 +182,4 @@ def __init__(
jupyter_notebook_stop.command,
jupyter_notebook_restart.command,
]
)
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import docker
import os

from common import Container, ContainerManager
from common.docker.exceptions import DockerError
Expand All @@ -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={
Expand All @@ -30,6 +39,7 @@ def start_container(
ports={
"8888/tcp": port,
},
volumes=volumes,
)

return container
Expand Down
21 changes: 19 additions & 2 deletions src/commands/jupyter_notebook/jupyter_notebook_module.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions src/common/docker/container_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
from typing import Any, AnyStr, Union

import socket
import docker
import docker.errors

Expand Down Expand Up @@ -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()
Expand Down
23 changes: 21 additions & 2 deletions src/common/prompt_types.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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