Skip to content

Commit 08a1f06

Browse files
author
lindakladivova
committed
refactoring and other refinement edits
1 parent f1c289e commit 08a1f06

File tree

8 files changed

+203
-118
lines changed

8 files changed

+203
-118
lines changed

gui/wxpython/jupyter_notebook/dialogs.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
@package jupyter_notebook.dialog
2+
@package jupyter_notebook.dialogs
33
44
@brief Integration of Jupyter Notebook to GUI.
55
@@ -14,22 +14,21 @@
1414
@author Linda Karlovska <linda.karlovska seznam.cz>
1515
"""
1616

17+
import os
1718
from pathlib import Path
18-
import grass.script as gs
1919

2020
import wx
2121

22+
from grass.workflows.directory import get_default_jupyter_workdir
23+
2224

2325
class JupyterStartDialog(wx.Dialog):
24-
"""Dialog for selecting working directory and Jupyter startup options."""
26+
"""Dialog for selecting Jupyter startup options."""
2527

2628
def __init__(self, parent):
27-
wx.Dialog.__init__(
28-
self, parent, title=_("Start Jupyter Notebook"), size=(500, 300)
29-
)
30-
env = gs.gisenv()
31-
mapset_path = Path(env["GISDBASE"]) / env["LOCATION_NAME"] / env["MAPSET"]
32-
self.default_dir = mapset_path / "notebooks"
29+
super().__init__(parent, title=_("Start Jupyter Notebook"), size=(500, 300))
30+
31+
self.default_dir = get_default_jupyter_workdir()
3332

3433
self.selected_dir = self.default_dir
3534
self.create_template = True
@@ -59,7 +58,7 @@ def __init__(self, parent):
5958
dir_sizer.Add(self.dir_picker, 0, wx.EXPAND | wx.ALL, 5)
6059
sizer.Add(dir_sizer, 0, wx.EXPAND | wx.ALL, 10)
6160

62-
# Jupyter startup section
61+
# Template preference section
6362
options_box = wx.StaticBox(self, label=_("Options"))
6463
options_sizer = wx.StaticBoxSizer(options_box, wx.VERTICAL)
6564

@@ -84,7 +83,6 @@ def __init__(self, parent):
8483

8584
sizer.Add(options_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
8685

87-
# OK / Cancel buttons
8886
btns = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
8987
sizer.Add(btns, 0, wx.EXPAND | wx.ALL, 10)
9088

@@ -105,7 +103,16 @@ def OnRadioToggle(self, event):
105103
def GetValues(self):
106104
"""Return selected working directory and template preference."""
107105
if self.radio_custom.GetValue():
108-
self.selected_dir = Path(self.dir_picker.GetPath())
106+
path = Path(self.dir_picker.GetPath())
107+
108+
if not os.access(path, os.W_OK) or not os.access(path, os.X_OK):
109+
wx.MessageBox(
110+
_("You do not have permission to write to the selected directory."),
111+
_("Error"),
112+
wx.ICON_ERROR,
113+
)
114+
return None
115+
self.selected_dir = path
109116
else:
110117
self.selected_dir = self.default_dir
111118

gui/wxpython/jupyter_notebook/panel.py

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
import wx
2020

2121
from main_window.page import MainPageBase
22-
from grass.workflows.directory import JupyterDirectoryManager
23-
from grass.workflows.server import JupyterServerInstance, JupyterServerRegistry
22+
from grass.workflows.environment import JupyterEnvironment
2423

2524
from .notebook import JupyterAuiNotebook
2625
from .toolbars import JupyterToolbar
@@ -47,13 +46,9 @@ def __init__(
4746
self._giface = giface
4847
self.statusbar = statusbar
4948
self.workdir = workdir
50-
5149
self.SetName("Jupyter")
5250

53-
self.directory_manager = JupyterDirectoryManager(
54-
workdir=self.workdir, create_template=create_template
55-
)
56-
self.server_manager = JupyterServerInstance(workdir=self.workdir)
51+
self.env = JupyterEnvironment(self.workdir, create_template)
5752

5853
self.toolbar = JupyterToolbar(parent=self)
5954
self.aui_notebook = JupyterAuiNotebook(parent=self)
@@ -72,31 +67,38 @@ def _layout(self):
7267
self.Layout()
7368

7469
def SetUpNotebookInterface(self):
75-
"""Start server and load files available in a working directory."""
76-
# Prepare the working directory (find all existing files, copy a template file if needed)
77-
self.directory_manager.prepare_files()
78-
79-
# Start the Jupyter server in the specified working directory
80-
self.server_manager.start_server()
81-
82-
# Register server to server registry
83-
JupyterServerRegistry.get().register(self.server_manager)
84-
85-
# Update the status bar with server info
86-
status_msg = _(
87-
"Jupyter server has started at {url} (PID: {pid}) in working directory {dir}"
88-
).format(
89-
url=self.server_manager.server_url,
90-
pid=self.server_manager.pid,
91-
dir=self.workdir,
92-
)
93-
self.SetStatusText(status_msg, 0)
70+
"""Setup Jupyter notebook environment and load initial notebooks."""
71+
try:
72+
self.env.setup()
73+
except Exception as e:
74+
wx.MessageBox(
75+
_("Failed to start Jupyter environment:\n{}").format(str(e)),
76+
_("Startup Error"),
77+
wx.ICON_ERROR,
78+
)
79+
return
9480

95-
# Load all existing files found in the working directory as separate tabs
96-
for fname in self.directory_manager.files:
97-
url = self.server_manager.get_url(fname.name)
81+
# Load notebook tabs
82+
for fname in self.env.directory.files:
83+
try:
84+
url = self.env.server.get_url(fname.name)
85+
except RuntimeError as e:
86+
wx.MessageBox(
87+
_("Failed to get Jupyter server URLt:\n{}").format(str(e)),
88+
_("Startup Error"),
89+
wx.ICON_ERROR,
90+
)
91+
return
9892
self.aui_notebook.AddPage(url=url, title=fname.name)
9993

94+
self.SetStatusText(
95+
_("Jupyter server started at {url} (PID: {pid}), directory: {dir}").format(
96+
url=self.env.server.server_url,
97+
pid=self.env.server.pid,
98+
dir=str(self.workdir),
99+
)
100+
)
101+
100102
def Switch(self, file_name):
101103
"""
102104
Switch to existing notebook tab.
@@ -114,9 +116,16 @@ def Open(self, file_name):
114116
Open a Jupyter notebook to a new tab and switch to it.
115117
:param file_name: Name of the .ipynb file (e.g., 'example.ipynb') (str).
116118
"""
117-
url = self.server_manager.get_url(file_name)
118-
self.aui_notebook.AddPage(url=url, title=file_name)
119-
self.aui_notebook.SetSelection(self.aui_notebook.GetPageCount() - 1)
119+
try:
120+
url = self.env.server.get_url(file_name)
121+
self.aui_notebook.AddPage(url=url, title=file_name)
122+
self.aui_notebook.SetSelection(self.aui_notebook.GetPageCount() - 1)
123+
except RuntimeError as e:
124+
wx.MessageBox(
125+
_("Failed to get Jupyter server URL:\n{}").format(str(e)),
126+
_("URL Error"),
127+
wx.ICON_ERROR,
128+
)
120129

121130
def OpenOrSwitch(self, file_name):
122131
"""
@@ -136,7 +145,7 @@ def Import(self, source_path, new_name=None):
136145
:param new_name: Optional new name for the imported file (str).
137146
"""
138147
try:
139-
path = self.directory_manager.import_file(source_path, new_name=new_name)
148+
path = self.env.directory.import_file(source_path, new_name=new_name)
140149
self.Open(path.name)
141150
self.SetStatusText(_("File '{}' imported and opened.").format(path.name), 0)
142151
except Exception as e:
@@ -169,7 +178,7 @@ def OnImport(self, event=None):
169178

170179
source_path = Path(dlg.GetPath())
171180
file_name = source_path.name
172-
target_path = self.directory_manager.workdir / file_name
181+
target_path = self.workdir / file_name
173182

174183
# File is already in the working directory
175184
if source_path.resolve() == target_path.resolve():
@@ -222,7 +231,7 @@ def OnExport(self, event=None):
222231
destination_path = Path(dlg.GetPath())
223232

224233
try:
225-
self.directory_manager.export_file(
234+
self.env.directory.export_file(
226235
file_name, destination_path, overwrite=True
227236
)
228237
self.SetStatusText(
@@ -254,7 +263,7 @@ def OnCreate(self, event=None):
254263
return
255264

256265
try:
257-
path = self.directory_manager.create_new_notebook(new_name=name)
266+
path = self.env.directory.create_new_notebook(new_name=name)
258267
except Exception as e:
259268
wx.MessageBox(
260269
_("Failed to create notebook:\n{}").format(str(e)),
@@ -287,25 +296,25 @@ def OnCloseWindow(self, event):
287296
event.Veto()
288297
return
289298

290-
if self.server_manager:
291-
try:
292-
# Stop the Jupyter server
293-
self.server_manager.stop_server()
294-
295-
# Unregister server from server registry
296-
JupyterServerRegistry.get().unregister(self.server_manager)
297-
self.SetStatusText(_("Jupyter server has been stopped."), 0)
298-
except RuntimeError as e:
299-
wx.MessageBox(
300-
_("Failed to stop the Jupyter server:\n{}").format(str(e)),
301-
_("Error"),
302-
wx.ICON_ERROR | wx.OK,
303-
)
304-
self.SetStatusText(_("Failed to stop Jupyter server."), 0)
299+
# Get server info
300+
url = self.env.server.server_url
301+
pid = self.env.server.pid
305302

306-
# Clean up the server manager
307-
if hasattr(self.GetParent(), "jupyter_server_manager"):
308-
self.GetParent().jupyter_server_manager = None
309-
310-
# Close the notebook panel
303+
# Stop server and close panel
304+
try:
305+
self.env.stop()
306+
except RuntimeError as e:
307+
wx.MessageBox(
308+
_("Failed to stop Jupyter server at {url} (PID: {pid}):\n{err}").format(
309+
url=url, pid=pid, err=str(e)
310+
),
311+
caption=_("Error"),
312+
style=wx.ICON_ERROR | wx.OK,
313+
)
314+
return
315+
self.SetStatusText(
316+
_("Jupyter server at {url} (PID: {pid}) has been stopped").format(
317+
url=url, pid=pid
318+
)
319+
)
311320
self._onCloseWindow(event)

gui/wxpython/main_window/frame.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ def OnGModeler(self, event=None, cmd=None):
907907
self.mainnotebook.AddPage(gmodeler_panel, _("Graphical Modeler"))
908908

909909
def OnJupyterNotebook(self, event=None, cmd=None):
910-
"""Launch Jupyter Notebook page. See OnJupyterNotebook documentation"""
910+
"""Launch Jupyter Notebook interface."""
911911
from jupyter_notebook.panel import JupyterPanel
912912
from jupyter_notebook.dialogs import JupyterStartDialog
913913

@@ -921,6 +921,9 @@ def OnJupyterNotebook(self, event=None, cmd=None):
921921
values = dlg.GetValues()
922922
dlg.Destroy()
923923

924+
if not values:
925+
return
926+
924927
workdir = values["directory"]
925928
create_template = values["create_template"]
926929

@@ -939,7 +942,7 @@ def OnJupyterNotebook(self, event=None, cmd=None):
939942
self.mainnotebook.AddPage(jupyter_panel, _("Jupyter Notebook"))
940943

941944
def OnShowJupyterInfo(self, event=None):
942-
"""Show information dialog when Jupyter Notebook is not installed."""
945+
"""Show information dialog when Jupyter Notebook is not available."""
943946
if sys.platform.startswith("win"):
944947
message = _(
945948
"Jupyter Notebook is currently not included in the Windows GRASS build process.\n"
@@ -2457,10 +2460,17 @@ def _closeWindow(self, event):
24572460
event.Veto()
24582461
return
24592462

2460-
from grass.workflows import JupyterServerRegistry
2461-
2462-
JupyterServerRegistry.get().stop_all_servers()
2463+
# Stop all running Jupyter servers before destroying the GUI
2464+
from grass.workflows import JupyterEnvironment
24632465

2466+
try:
2467+
JupyterEnvironment.stop_all()
2468+
except RuntimeError as e:
2469+
wx.MessageBox(
2470+
_("Failed to stop Jupyter servers:\n{}").format(str(e)),
2471+
caption=_("Error"),
2472+
style=wx.ICON_ERROR | wx.OK,
2473+
)
24642474
self.DisplayCloseAll()
24652475

24662476
self._auimgr.UnInit()

python/grass/workflows/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ include $(MODULE_TOPDIR)/include/Make/Python.make
55

66
DSTDIR = $(ETC)/python/grass/workflows
77
TEMPLATE_FILES = template_notebooks/welcome.ipynb template_notebooks/new.ipynb
8-
MODULES = server directory
8+
MODULES = server directory environment
99

1010
PYFILES = $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
1111
PYCFILES = $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)

python/grass/workflows/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
This module provides functionality for:
1717
- Starting and stopping local Jupyter Notebook servers inside a GRASS session
18-
- Managing notebook directories linked to specific GRASS mapsets
18+
- Managing notebook working directories
1919
- Creating default notebook templates for users
2020
- Supporting integration with the GUI (e.g., wxGUI) and other tools
2121
@@ -32,10 +32,13 @@
3232

3333
from .server import JupyterServerInstance, JupyterServerRegistry
3434
from .directory import JupyterDirectoryManager
35+
from .environment import JupyterEnvironment
3536

3637
__all__ = [
3738
"Directory",
39+
"Environment",
3840
"JupyterDirectoryManager",
41+
"JupyterEnvironment",
3942
"JupyterServerInstance",
4043
"JupyterServerRegistry",
4144
"Server",

0 commit comments

Comments
 (0)