+
+ {% set
+ sections = (
+ (
+ ("http://nbviewer.jupyter.org/github/ipython/ipython/blob/3.x/examples/Notebook/Index.ipynb", _("Notebook Help"), True),
+ ("https://help.github.com/articles/markdown-basics/",_("Markdown"),True),
+ ),
+ )
+ %}
+
+ {% set openmsg = _("Opens in a new window") %}
+ {% for helplinks in sections %}
+ {% for link in helplinks %}
+
+ UPDATE
+ Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions
+ -
+
+ Please note that updating to Notebook 7 might break some of your extensions.
+
-
- {% set
- sections = (
- (
- ("http://nbviewer.jupyter.org/github/ipython/ipython/blob/3.x/examples/Notebook/Index.ipynb", _("Notebook Help"), True),
- ("https://help.github.com/articles/markdown-basics/",_("Markdown"),True),
- ),
- )
- %}
-
- {% set openmsg = _("Opens in a new window") %}
- {% for helplinks in sections %}
- {% for link in helplinks %}
-
- UPDATE
- Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions
- -
-
- Please note that updating to Notebook 7 might break some of your extensions.
-
-
-{% block after_site %}
-{% endblock %}
-
-{% block script %}
-{% endblock %}
-
-
-
-
-
diff --git a/appmode/server_extension.py b/appmode/server_extension.py
index 7985c74..b590356 100644
--- a/appmode/server_extension.py
+++ b/appmode/server_extension.py
@@ -5,6 +5,7 @@
from notebook.utils import url_path_join
from notebook.base.handlers import IPythonHandler, FilesRedirectHandler, path_regex
import notebook.notebook.handlers as orig_handler
+import notebook
import collections.abc
from tornado import web
from traitlets.config import LoggingConfigurable
@@ -19,6 +20,7 @@ class Appmode(LoggingConfigurable):
show_edit_button = Bool(True, help="Show Edit App button during Appmode.", config=True)
show_other_buttons = Bool(True, help="Show other buttons, e.g. Logout, during Appmode.", config=True)
temp_dir = Unicode('', help="Create temporary Appmode notebooks in this directory.", config=True)
+ hidden_temp_files = Bool(True, help="Temporary Appmode notebooks are hidden files.", config=True)
#===============================================================================
class AppmodeHandler(IPythonHandler):
@@ -38,6 +40,10 @@ def show_other_buttons(self):
def temp_dir(self):
return self.settings['appmode'].temp_dir
+ @property
+ def hidden_temp_files(self):
+ return self.settings['appmode'].hidden_temp_files
+
#===========================================================================
@web.authenticated
async def get(self, path):
@@ -124,6 +130,8 @@ async def mk_tmp_copy(self, path):
os.makedirs(dirname)
fullbasename = os.path.basename(path)
basename, ext = os.path.splitext(fullbasename)
+ if self.hidden_temp_files:
+ basename = '.' + basename
for i in itertools.count():
tmp_path = "%s/%s-%i%s"%(dirname, basename, i, ext)
if not cm.exists(tmp_path):
@@ -139,8 +147,14 @@ async def mk_tmp_copy(self, path):
#===============================================================================
def load_jupyter_server_extension(nbapp):
tmpl_dir = os.path.dirname(__file__)
+ notebook_tmpl_dir = os.path.join(notebook.__path__[0], 'templates')
# does not work, because init_webapp() happens before init_server_extensions()
- #nbapp.extra_template_paths.append(tmpl_dir) # dows
+ # nbapp.extra_template_paths.append(tmpl_dir) # dows
+
+ # For jupyter server, the notebook templates are not available in the default search paths. This can be addressed
+ # by using --ServerApp.extra_template_paths='***site-packages***\notebook\templates', but this is messy.
+ # To emulate this instead insert the notebook template directory at the start of the searchpath
+ # These will be used last, so the notebook.html resolves, but the page.html is still from jupyter server templates
# For configuration values that can be set server side
appmode = Appmode(parent=nbapp)
@@ -151,6 +165,7 @@ def load_jupyter_server_extension(nbapp):
for loader in getattr(rootloader, 'loaders', [rootloader]):
if hasattr(loader, 'searchpath') and tmpl_dir not in loader.searchpath:
loader.searchpath.append(tmpl_dir)
+ loader.searchpath.insert(0, notebook_tmpl_dir)
web_app = nbapp.web_app
host_pattern = '.*$'
From 75c25cd9c7e2ed7bd7ed2a70646b6d2a41740759 Mon Sep 17 00:00:00 2001
From: pdstack <125979306+pdstack@users.noreply.github.com>
Date: Sun, 5 Mar 2023 17:52:04 +1100
Subject: [PATCH 3/5] Swap over path reference for notebook templates to use
__file__ instead of __path__ for consistency
---
appmode/server_extension.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/appmode/server_extension.py b/appmode/server_extension.py
index b590356..24a2dc9 100644
--- a/appmode/server_extension.py
+++ b/appmode/server_extension.py
@@ -147,7 +147,7 @@ async def mk_tmp_copy(self, path):
#===============================================================================
def load_jupyter_server_extension(nbapp):
tmpl_dir = os.path.dirname(__file__)
- notebook_tmpl_dir = os.path.join(notebook.__path__[0], 'templates')
+ notebook_tmpl_dir = os.path.join(os.path.dirname(notebook.__file__), 'templates')
# does not work, because init_webapp() happens before init_server_extensions()
# nbapp.extra_template_paths.append(tmpl_dir) # dows
From 46090e38b72fb97822b78e25e909f474d346e0fb Mon Sep 17 00:00:00 2001
From: pdstack <125979306+pdstack@users.noreply.github.com>
Date: Fri, 10 Mar 2023 00:40:24 +1100
Subject: [PATCH 4/5] Added ensure_async wrapper for session manager and
content manager calls
---
appmode/server_extension.py | 35 +++++++++++++++++++----------------
1 file changed, 19 insertions(+), 16 deletions(-)
diff --git a/appmode/server_extension.py b/appmode/server_extension.py
index 24a2dc9..5fce8d5 100644
--- a/appmode/server_extension.py
+++ b/appmode/server_extension.py
@@ -1,17 +1,29 @@
# -*- coding: utf-8 -*-
import os
+import inspect
import itertools
from notebook.utils import url_path_join
from notebook.base.handlers import IPythonHandler, FilesRedirectHandler, path_regex
import notebook.notebook.handlers as orig_handler
import notebook
-import collections.abc
from tornado import web
from traitlets.config import LoggingConfigurable
from traitlets import Bool, Unicode
+async def ensure_async(obj):
+ """Convert a non-awaitable object to a coroutine if needed,
+ and await if it was a coroutine.
+
+ Designed to be called on the result of calling a function,
+ when that function could be asynchronous or not.
+ """
+ if inspect.isawaitable(obj):
+ obj = await obj
+ return obj
+
+
class Appmode(LoggingConfigurable):
"""Object containing server-side configuration settings for Appmode.
Defined separately from the AppmodeHandler to avoid multiple inheritance.
@@ -60,9 +72,7 @@ async def get(self, path):
cm = self.contents_manager
# will raise 404 on not found
try:
- model = cm.get(path, content=False)
- if isinstance(model, collections.abc.Awaitable):
- model = await model
+ model = await ensure_async(cm.get(path, content=False))
except web.HTTPError as e:
if e.status_code == 404 and 'files' in path.split('/'):
# 404, but '/files/' in URL, let FilesRedirect take care of it
@@ -107,18 +117,12 @@ async def post(self, path):
# delete session, including the kernel
sm = self.session_manager
- s = sm.get_session(path=path)
- if isinstance(s, collections.abc.Awaitable):
- s = await s
- sd = sm.delete_session(session_id=s['id'])
- if isinstance(sd, collections.abc.Awaitable):
- await sd
+ s = await ensure_async(sm.get_session(path=path))
+ await ensure_async(sm.delete_session(session_id=s['id']))
# delete tmp copy
cm = self.contents_manager
- pd = cm.delete(path)
- if isinstance(pd, collections.abc.Awaitable):
- await pd
+ await ensure_async(cm.delete(path))
await self.finish()
#===========================================================================
@@ -139,11 +143,10 @@ async def mk_tmp_copy(self, path):
# create tmp copy - allows opening same notebook multiple times
self.log.info("Appmode creating tmp copy: "+tmp_path)
- pc = cm.copy(path, tmp_path)
- if isinstance(pc, collections.abc.Awaitable):
- await pc
+ await ensure_async(cm.copy(path, tmp_path))
return tmp_path
+
#===============================================================================
def load_jupyter_server_extension(nbapp):
tmpl_dir = os.path.dirname(__file__)
From 16740bec0a02b528d4f0c208ce360abdc59cf7d8 Mon Sep 17 00:00:00 2001
From: pdstack <125979306+pdstack@users.noreply.github.com>
Date: Fri, 10 Mar 2023 02:21:41 +1100
Subject: [PATCH 5/5] Rename ensure_async to await_if_awaitable
---
appmode/server_extension.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/appmode/server_extension.py b/appmode/server_extension.py
index 5fce8d5..717e149 100644
--- a/appmode/server_extension.py
+++ b/appmode/server_extension.py
@@ -12,7 +12,7 @@
from traitlets import Bool, Unicode
-async def ensure_async(obj):
+async def await_if_awaitable(obj):
"""Convert a non-awaitable object to a coroutine if needed,
and await if it was a coroutine.
@@ -72,7 +72,7 @@ async def get(self, path):
cm = self.contents_manager
# will raise 404 on not found
try:
- model = await ensure_async(cm.get(path, content=False))
+ model = await await_if_awaitable(cm.get(path, content=False))
except web.HTTPError as e:
if e.status_code == 404 and 'files' in path.split('/'):
# 404, but '/files/' in URL, let FilesRedirect take care of it
@@ -117,12 +117,12 @@ async def post(self, path):
# delete session, including the kernel
sm = self.session_manager
- s = await ensure_async(sm.get_session(path=path))
- await ensure_async(sm.delete_session(session_id=s['id']))
+ s = await await_if_awaitable(sm.get_session(path=path))
+ await await_if_awaitable(sm.delete_session(session_id=s['id']))
# delete tmp copy
cm = self.contents_manager
- await ensure_async(cm.delete(path))
+ await await_if_awaitable(cm.delete(path))
await self.finish()
#===========================================================================
@@ -143,7 +143,7 @@ async def mk_tmp_copy(self, path):
# create tmp copy - allows opening same notebook multiple times
self.log.info("Appmode creating tmp copy: "+tmp_path)
- await ensure_async(cm.copy(path, tmp_path))
+ await await_if_awaitable(cm.copy(path, tmp_path))
return tmp_path