Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-jupyter-server for openSUSE:Factory checked in at 2021-02-15 23:20:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jupyter-server (Old) and /work/SRC/openSUSE:Factory/.python-jupyter-server.new.28504 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server" Mon Feb 15 23:20:21 2021 rev:11 rq:872453 version:1.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jupyter-server/python-jupyter-server.changes 2021-02-10 21:30:06.502260260 +0100 +++ /work/SRC/openSUSE:Factory/.python-jupyter-server.new.28504/python-jupyter-server.changes 2021-02-15 23:22:02.979911033 +0100 @@ -1,0 +2,15 @@ +Sun Feb 14 18:05:36 UTC 2021 - Arun Persaud <a...@gmx.de> + +- update to version 1.3.0: + * Special case ExtensionApp that starts the ServerApp #401 (afshin) + * only use deprecated notebook_dir config if root_dir is not set + #400 (minrk) + * Use async kernel manager by default #399 (kevin-bates) + * Revert Session.username default value change #398 (mwakaba2) + * Re-enable default_url in ExtensionApp #393 (afshin) + * Enable notebook ContentsManager in jupyter_server #392 (afshin) + * Use jupyter_server_config.json as config file in the update + password api #390 (echarles) + * Increase culling test idle timeout #388 (kevin-bates) + +------------------------------------------------------------------- Old: ---- jupyter_server-1.2.3.tar.gz New: ---- jupyter_server-1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jupyter-server.spec ++++++ --- /var/tmp/diff_new_pack.9sfjgF/_old 2021-02-15 23:22:03.719912138 +0100 +++ /var/tmp/diff_new_pack.9sfjgF/_new 2021-02-15 23:22:03.723912144 +0100 @@ -19,15 +19,13 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-jupyter-server -Version: 1.2.3 +Version: 1.3.0 Release: 0 Summary: The Jupyter Server License: BSD-3-Clause Group: Development/Languages/Python URL: https://github.com/jupyter/jupyter_server Source: https://github.com/jupyter/jupyter_server/archive/%{version}.tar.gz#/jupyter_server-%{version}.tar.gz -# We need the full stdlib -BuildRequires: %pythons BuildRequires: %{python_module Jinja2} BuildRequires: %{python_module Send2Trash} BuildRequires: %{python_module anyio >= 2.0.2} @@ -42,6 +40,8 @@ BuildRequires: %{python_module terminado >= 0.8.3} BuildRequires: %{python_module tornado >= 6.1} BuildRequires: %{python_module traitlets >= 4.2.1} +# We need the full stdlib +BuildRequires: %{pythons} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-Jinja2 @@ -59,6 +59,8 @@ Requires: python-traitlets >= 4.2.1 Requires(post): update-alternatives Requires(postun): update-alternatives +Provides: python-jupyter_server = %{version}-%{release} +Obsoletes: python-jupyter_server < %{version}-%{release} # SECTION extras_require test BuildRequires: %{python_module ipykernel} BuildRequires: %{python_module pytest-console-scripts} @@ -66,8 +68,6 @@ BuildRequires: %{python_module pytest} BuildRequires: %{python_module requests} # /SECTION -Provides: python-jupyter_server = %{version}-%{release} -Obsoletes: python-jupyter_server < %{version}-%{release} %if "%{python_flavor}" == "python3" || "%{python_provides}" == "python3" Provides: jupyter-jupyter-server = %{version}-%{release} Obsoletes: jupyter-jupyter-server < %{version}-%{release} ++++++ jupyter_server-1.2.3.tar.gz -> jupyter_server-1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/CHANGELOG.md new/jupyter_server-1.3.0/CHANGELOG.md --- old/jupyter_server-1.2.3/CHANGELOG.md 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/CHANGELOG.md 2021-02-05 02:40:21.000000000 +0100 @@ -2,8 +2,22 @@ All notable changes to this project will be documented in this file. +## [1.3.0](https://github.com/jupyter-server/jupyter_server/tree/1.3.0) (2021-02-4) + +[Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/1.2.3...HEAD) + +**Merged pull requests:** + +- Special case ExtensionApp that starts the ServerApp [\#401](https://github.com/jupyter-server/jupyter_server/pull/401) ([afshin](https://github.com/afshin)) +- only use deprecated notebook\_dir config if root\_dir is not set [\#400](https://github.com/jupyter-server/jupyter_server/pull/400) ([minrk](https://github.com/minrk)) +- Use async kernel manager by default [\#399](https://github.com/jupyter-server/jupyter_server/pull/399) ([kevin-bates](https://github.com/kevin-bates)) +- Revert Session.username default value change [\#398](https://github.com/jupyter-server/jupyter_server/pull/398) ([mwakaba2](https://github.com/mwakaba2)) + + ## [1.2.3](https://github.com/jupyter-server/jupyter_server/tree/1.2.3) (2021-01-29) +This was a broken release and was yanked from PyPI. + [Full Changelog](https://github.com/jupyter-server/jupyter_server/compare/1.2.2...HEAD) **Merged pull requests:** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/docs/source/developers/extensions.rst new/jupyter_server-1.3.0/docs/source/developers/extensions.rst --- old/jupyter_server-1.2.3/docs/source/developers/extensions.rst 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/docs/source/developers/extensions.rst 2021-02-05 02:40:21.000000000 +0100 @@ -128,7 +128,7 @@ # -------------- Required traits -------------- name = "myextension" - extension_url = "/myextension" + default_url = "/myextension" load_other_extensions = True # --- ExtensionApp traits you can configure --- @@ -167,7 +167,7 @@ Properties * ``name``: the name of the extension -* ``extension_url``: the default url for this extension???i.e. the landing page for this extension when launched from the CLI. +* ``default_url``: the default url for this extension???i.e. the landing page for this extension when launched from the CLI. * ``load_other_extensions``: a boolean enabling/disabling other extensions when launching this extension directly. ``ExtensionApp`` request handlers @@ -302,13 +302,13 @@ ``ExtensionApp`` as a classic Notebook server extension ------------------------------------------------------- -An extension that extends ``ExtensionApp`` should still work with the old Tornado server from the classic Jupyter Notebook. The ``ExtensionApp`` class +An extension that extends ``ExtensionApp`` should still work with the old Tornado server from the classic Jupyter Notebook. The ``ExtensionApp`` class provides a method, ``load_classic_server_extension``, that handles the extension initialization. Simply define a ``load_jupyter_server_extension`` reference -pointing at the ``load_classic_server_extension`` method: +pointing at the ``load_classic_server_extension`` method: .. code-block:: python - # This is typically defined in the root `__init__.py` + # This is typically defined in the root `__init__.py` # file of the extension package. load_jupyter_server_extension = MyExtensionApp.load_classic_server_extension @@ -483,7 +483,7 @@ .. code-block:: python def load_jupyter_server_extension(nb_server_app): - + web_app = nb_server_app.web_app host_pattern = '.*$' base_url = web_app.settings['base_url'] @@ -495,50 +495,50 @@ # Favicon redirects. favicon_redirects = [ - ( - url_path_join(base_url, "/static/favicons/favicon.ico"), + ( + url_path_join(base_url, "/static/favicons/favicon.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon.ico") ), ( - url_path_join(base_url, "/static/favicons/favicon-busy-1.ico"), + url_path_join(base_url, "/static/favicons/favicon-busy-1.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-1.ico")} ), ( - url_path_join(base_url, "/static/favicons/favicon-busy-2.ico"), + url_path_join(base_url, "/static/favicons/favicon-busy-2.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-2.ico")} ), ( - url_path_join(base_url, "/static/favicons/favicon-busy-3.ico"), + url_path_join(base_url, "/static/favicons/favicon-busy-3.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-3.ico")} ), ( - url_path_join(base_url, "/static/favicons/favicon-file.ico"), + url_path_join(base_url, "/static/favicons/favicon-file.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-file.ico")} ), ( - url_path_join(base_url, "/static/favicons/favicon-notebook.ico"), + url_path_join(base_url, "/static/favicons/favicon-notebook.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-notebook.ico")} ), ( - url_path_join(base_url, "/static/favicons/favicon-terminal.ico"), + url_path_join(base_url, "/static/favicons/favicon-terminal.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-terminal.ico")} ), ( - url_path_join(base_url, "/static/logo/logo.png"), + url_path_join(base_url, "/static/logo/logo.png"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/logo.png")} ), ] web_app.add_handlers( - host_pattern, + host_pattern, custom_handlers + favicon_redirects ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/docs/source/other/full-config.rst new/jupyter_server-1.3.0/docs/source/other/full-config.rst --- old/jupyter_server-1.2.3/docs/source/other/full-config.rst 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/docs/source/other/full-config.rst 2021-02-05 02:40:21.000000000 +0100 @@ -110,9 +110,9 @@ Default: ``''`` Set the Access-Control-Allow-Origin header - + Use '*' to allow any origin to access your server. - + Takes precedence over allow_origin_pat. @@ -120,13 +120,13 @@ Default: ``''`` Use a regular expression for the Access-Control-Allow-Origin header - + Requests from an origin matching the expression will get replies with: - + Access-Control-Allow-Origin: origin - + where `origin` is the origin of the request. - + Ignored if allow_origin is set. @@ -134,11 +134,11 @@ Default: ``True`` Allow password to be changed at login for the Jupyter server. - + While loggin in with a token, the Jupyter server UI will give the opportunity to the user to enter a new password at the same time that will replace the token login mechanism. - + This can be set to false to prevent changing password from the UI/API. @@ -146,15 +146,15 @@ Default: ``False`` Allow requests where the Host header doesn't point to a local server - + By default, requests get a 403 forbidden response if the 'Host' header shows that the browser thinks it's on a non-local domain. Setting this option to True disables this check. - + This protects against 'DNS rebinding' attacks, where a remote web server serves you a page and then changes its DNS to send later requests to a local IP, bypassing same-origin checks. - + Local IP addresses (such as 127.0.0.1 and ::1) are allowed as local, along with hostnames configured in local_hostnames. @@ -173,7 +173,7 @@ Default: ``'/'`` The base URL for the Jupyter server. - + Leading and trailing slashes can be omitted, and will automatically be added. @@ -229,7 +229,7 @@ The random bytes used to secure cookies. By default this is a new random number every time you start the server. Set it to a value in a config file to enable logins to persist across server sessions. - + Note: Cookie secrets should be kept private, do not share config files with cookie_secret stored in plaintext (you can read the value from a file). @@ -243,12 +243,12 @@ Default: ``''`` Override URL shown to users. - + Replace actual URL, including protocol, address, port and base URL, with the given value when displaying URL to the users. Do not change the actual connection URL. If authentication token is enabled, the token is added to the custom URL automatically. - + This option is intended to be used when the URL to display to the user cannot be determined reliably by the Jupyter server (proxified or containerized setups for example). @@ -262,13 +262,13 @@ Default: ``False`` Disable cross-site-request-forgery protection - + Jupyter notebook 4.3.1 introduces protection from cross-site request forgeries, requiring API requests to either: - + - originate from pages served by this server (validated with XSRF cookie and token), or - authenticate with a token - + Some anonymous compute resources still desire the ability to run code, completely without authentication. These services can disable all authentication and security checks, @@ -284,7 +284,7 @@ Default: ``[]`` Extra paths to search for serving static files. - + This allows adding javascript/css to be available from the Jupyter server machine, or overriding individual files in the IPython @@ -292,7 +292,7 @@ Default: ``[]`` Extra paths to search for serving jinja templates. - + Can be used to override templates from jupyter_server.templates. ServerApp.file_to_run : Unicode @@ -352,10 +352,10 @@ ServerApp.kernel_spec_manager_class : Type Default: ``'jupyter_client.kernelspec.KernelSpecManager'`` - + The kernel spec manager class to use. Should be a subclass of `jupyter_client.kernelspec.KernelSpecManager`. - + The Api of KernelSpecManager is provisional and might change without warning between this version of Jupyter and the next stable one. @@ -369,7 +369,7 @@ Default: ``['localhost']`` Hostnames to allow as local when allow_remote_access is False. - + Local IP addresses (such as 127.0.0.1 and ::1) are automatically accepted as local as well. @@ -402,19 +402,19 @@ ServerApp.max_body_size : Int Default: ``536870912`` - + Sets the maximum allowed size of the client request body, specified in the Content-Length request header field. If the size in a request exceeds the configured value, a malformed HTTP message is returned to the client. - + Note: max_body_size is applied even in streaming mode. ServerApp.max_buffer_size : Int Default: ``536870912`` - + Gets or sets the maximum amount of memory, in bytes, that is allocated for use by the buffer manager. @@ -438,11 +438,11 @@ Default: ``''`` Hashed password to use for web authentication. - + To generate, type in a python/IPython shell: - + from jupyter_server.auth import passwd; passwd() - + The string should be of the form type:salt:hashed-password. @@ -452,10 +452,10 @@ Forces users to use a password for the Jupyter server. This is useful in a multi user environment, for instance when everybody in the LAN can access each other's machine through ssh. - + In such a case, serving on localhost is not secure since any user can connect to the Jupyter server via ssh. - + ServerApp.port : Int @@ -471,7 +471,7 @@ ServerApp.pylab : Unicode Default: ``'disabled'`` - + DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. @@ -531,10 +531,10 @@ Default: ``True`` Set to False to disable terminals. - + This does *not* make the server more secure by itself. Anything the user can in a terminal, they can also do in a notebook. - + Terminals may also be automatically disabled if the terminado package is not available. @@ -543,10 +543,10 @@ Default: ``'<generated>'`` Token used for authenticating first-time connections to the server. - + When no password is enabled, the default is to generate a new, random token. - + Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED. @@ -567,24 +567,24 @@ `new` argument passed to the standard library method `webbrowser.open`. The behaviour is not guaranteed, but depends on browser support. Valid values are: - + - 2 opens a new tab, - 1 opens a new window, - 0 opens in an existing window. - + See the `webbrowser.open` documentation for details. ServerApp.websocket_compression_options : Any Default: ``None`` - + Set the tornado compression options for websocket connections. - + This value will be returned from :meth:`WebSocketHandler.get_compression_options`. None (default) will disable compression. A dict (even an empty one) will enable compression. - + See the tornado docs for WebSocketHandler.get_compression_options for details. @@ -593,7 +593,7 @@ The base URL for websockets, if it differs from the HTTP server (hint: it almost certainly doesn't). - + Should be in the form of an HTTP origin: ws[s]://hostname[:port] @@ -601,7 +601,7 @@ Default: ``''`` JSON file in which to store connection info [default: kernel-<pid>.json] - + This file will contain the IP, ports, and authentication key needed to connect clients to this kernel. By default, this file will be created in the security dir of the current profile, but can be specified by absolute path. @@ -654,7 +654,7 @@ Default: ``''`` JSON file in which to store connection info [default: kernel-<pid>.json] - + This file will contain the IP, ports, and authentication key needed to connect clients to this kernel. By default, this file will be created in the security dir of the current profile, but can be specified by absolute path. @@ -687,7 +687,7 @@ Default: ``[]`` DEPRECATED: Use kernel_name instead. - + The Popen Command to launch the kernel. Override this if you have a custom kernel. If kernel_cmd is specified in a configuration file, @@ -727,7 +727,7 @@ Default: ``True`` Whether to check PID to protect against calls after fork. - + This check can be disabled if fork-safety is handled elsewhere. @@ -745,7 +745,7 @@ Default: ``65536`` The maximum number of digests to remember. - + The digest history will be culled when it exceeds this value. @@ -796,7 +796,7 @@ Only used with custom functions for `packer`. Session.username : Unicode - Default: ``'mwakabayashi'`` + Default: ``'username'`` Username for the Session. Default is your system username. @@ -833,10 +833,10 @@ Default: ``True`` Whether messages from kernels whose frontends have disconnected should be buffered in-memory. - + When True (default), messages are buffered and replayed on reconnect, avoiding lost messages due to interrupted connectivity. - + Disable if long-running kernels will produce too much output while no frontends are connected. @@ -874,7 +874,7 @@ Default: ``60`` Timeout for giving up on a kernel (in seconds). - + On starting and restarting kernels, we check whether the kernel is running and responsive by sending kernel_info_requests. This sets the timeout in seconds for how long the kernel can take @@ -923,7 +923,7 @@ Default: ``set()`` Whitelist of allowed kernel names. - + By default, all installed kernels are allowed. @@ -951,13 +951,13 @@ Default: ``'jupyter_server.files.handlers.FilesHandler'`` handler class to use when serving raw file requests. - + Default is a fallback that talks to the ContentsManager API, which may be inefficient, especially for large files. - + Local files-based ContentsManagers can use a StaticFileHandler subclass, which will be much more efficient. - + Access to these files should be Authenticated. @@ -965,7 +965,7 @@ Default: ``{}`` Extra parameters to pass to files_handler_class. - + For example, StaticFileHandlers generally expect a `path` argument specifying the root directory from which to serve files. @@ -973,7 +973,7 @@ ContentsManager.hide_globs : List Default: ``['__pycache__', '*.pyc', '*.pyo', '.DS_Store', '*.so', '*.dyl...`` - + Glob patterns to hide in file and directory listings. @@ -981,17 +981,17 @@ Default: ``None`` Python callable or importstring thereof - + To be called on a contents model prior to save. - + This can be used to process the structure, such as removing notebook outputs or other side effects that should not be saved. - + It will be called as (all arguments passed by keyword):: - + hook(path=path, model=model, contents_manager=self) - + - model: the model to be saved. Includes file contents. Modifying this dict will affect the file that is stored. - path: the API path of the save destination @@ -1056,13 +1056,13 @@ Default: ``'jupyter_server.files.handlers.FilesHandler'`` handler class to use when serving raw file requests. - + Default is a fallback that talks to the ContentsManager API, which may be inefficient, especially for large files. - + Local files-based ContentsManagers can use a StaticFileHandler subclass, which will be much more efficient. - + Access to these files should be Authenticated. @@ -1070,7 +1070,7 @@ Default: ``{}`` Extra parameters to pass to files_handler_class. - + For example, StaticFileHandlers generally expect a `path` argument specifying the root directory from which to serve files. @@ -1078,7 +1078,7 @@ FileContentsManager.hide_globs : List Default: ``['__pycache__', '*.pyc', '*.pyo', '.DS_Store', '*.so', '*.dyl...`` - + Glob patterns to hide in file and directory listings. @@ -1086,16 +1086,16 @@ Default: ``None`` Python callable or importstring thereof - + to be called on the path of a file just saved. - + This can be used to process the file on disk, such as converting the notebook to a script or HTML via nbconvert. - + It will be called as (all arguments passed by keyword):: - + hook(os_path=os_path, model=model, contents_manager=instance) - + - path: the filesystem path to the file just written - model: the model representing the file - contents_manager: this ContentsManager instance @@ -1105,17 +1105,17 @@ Default: ``None`` Python callable or importstring thereof - + To be called on a contents model prior to save. - + This can be used to process the structure, such as removing notebook outputs or other side effects that should not be saved. - + It will be called as (all arguments passed by keyword):: - + hook(path=path, model=model, contents_manager=self) - + - model: the model to be saved. Includes file contents. Modifying this dict will affect the file that is stored. - path: the API path of the save destination @@ -1194,10 +1194,10 @@ Default: ``True`` Whether messages from kernels whose frontends have disconnected should be buffered in-memory. - + When True (default), messages are buffered and replayed on reconnect, avoiding lost messages due to interrupted connectivity. - + Disable if long-running kernels will produce too much output while no frontends are connected. @@ -1235,7 +1235,7 @@ Default: ``60`` Timeout for giving up on a kernel (in seconds). - + On starting and restarting kernels, we check whether the kernel is running and responsive by sending kernel_info_requests. This sets the timeout in seconds for how long the kernel can take @@ -1284,7 +1284,7 @@ Default: ``set()`` Whitelist of allowed kernel names. - + By default, all installed kernels are allowed. @@ -1388,4 +1388,3 @@ The websocket url of the Kernel or Enterprise Gateway server. If not provided, this value will correspond to the value of the Gateway url with 'ws' in place of 'http'. (JUPYTER_GATEWAY_WS_URL env var) - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/_version.py new/jupyter_server-1.3.0/jupyter_server/_version.py --- old/jupyter_server-1.2.3/jupyter_server/_version.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/_version.py 2021-02-05 02:40:21.000000000 +0100 @@ -11,5 +11,5 @@ # However, be sure to INCLUDE a dot prefix when adding the dev release: X.Y.Z.devN # See: https://www.python.org/dev/peps/pep-0440/#public-version-identifiers -version_info = (1, 2, 3, '') +version_info = (1, 3, 0, '') __version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/extension/application.py new/jupyter_server-1.3.0/jupyter_server/extension/application.py --- old/jupyter_server-1.2.3/jupyter_server/extension/application.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/extension/application.py 2021-02-05 02:40:21.000000000 +0100 @@ -4,6 +4,7 @@ from jinja2 import Environment, FileSystemLoader +from traitlets.config import Config from traitlets import ( HasTraits, Unicode, @@ -12,7 +13,6 @@ Bool, default ) -from traitlets.config import Config from tornado.log import LogFormatter from tornado.web import RedirectHandler @@ -186,6 +186,9 @@ def _default_url(self): return self.extension_url + # Is this linked to a serverapp yet? + _linked = Bool(False) + # Extension can configure the ServerApp from the command-line classes = [ ServerApp, @@ -196,9 +199,6 @@ _log_formatter_cls = LogFormatter - # Whether this app is the starter app - _is_starter_app = False - @default('log_level') def _default_log_level(self): return logging.INFO @@ -333,14 +333,14 @@ }) self.initialize_templates() - @classmethod - def _jupyter_server_config(cls): + def _jupyter_server_config(self): base_config = { "ServerApp": { - "jpserver_extensions": {cls.get_extension_package(): True}, + "default_url": self.default_url, + "open_browser": self.open_browser } } - base_config["ServerApp"].update(cls.serverapp_config) + base_config["ServerApp"].update(self.serverapp_config) return base_config def _link_jupyter_server_extension(self, serverapp): @@ -351,6 +351,10 @@ the command line contains traits for the ExtensionApp or the ExtensionApp's config files have server settings. + + Note, the ServerApp has not initialized the Tornado + Web Application yet, so do not try to affect the + `web_app` attribute. """ self.serverapp = serverapp # Load config from an ExtensionApp's config files. @@ -370,23 +374,8 @@ # ServerApp, do it here. # i.e. ServerApp traits <--- ExtensionApp config self.serverapp.update_config(self.config) - - @classmethod - def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs): - """Creates an instance of ServerApp where this extension is enabled - (superceding disabling found in other config from files). - - This is necessary when launching the ExtensionApp directly from - the `launch_instance` classmethod. - """ - # The ExtensionApp needs to add itself as enabled extension - # to the jpserver_extensions trait, so that the ServerApp - # initializes it. - config = Config(cls._jupyter_server_config()) - serverapp = ServerApp.instance(**kwargs, argv=[], config=config) - cls._is_starter_app = True - serverapp.initialize(argv=argv, find_extensions=load_other_extensions) - return serverapp + # Acknowledge that this extension has been linked. + self._linked = True def initialize(self): """Initialize the extension app. The @@ -440,12 +429,7 @@ except KeyError: extension = cls() extension._link_jupyter_server_extension(serverapp) - if cls._is_starter_app: - serverapp._starter_app = extension extension.initialize() - # Set the serverapp's default url to the extension's url. - if cls._is_starter_app: - serverapp.default_url = extension.default_url return extension @classmethod @@ -479,6 +463,24 @@ extension.initialize() @classmethod + def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs): + """Creates an instance of ServerApp and explicitly sets + this extension to enabled=True (i.e. superceding disabling + found in other config from files). + + The `launch_instance` method uses this method to initialize + and start a server. + """ + serverapp = ServerApp.instance( + jpserver_extensions={cls.get_extension_package(): True}, **kwargs) + serverapp.initialize( + argv=argv, + starter_extension=cls.name, + find_extensions=cls.load_other_extensions, + ) + return serverapp + + @classmethod def launch_instance(cls, argv=None, **kwargs): """Launch the extension like an application. Initializes+configs a stock server and appends the extension to the server. Then starts the server and routes to @@ -489,27 +491,29 @@ args = sys.argv[1:] # slice out extension config. else: args = argv - # Check for subcommands + + # Handle all "stops" that could happen before + # continuing to launch a server+extension. subapp = _preparse_for_subcommand(cls, args) if subapp: subapp.start() - else: - # Check for help, version, and generate-config arguments - # before initializing server to make sure these - # arguments trigger actions from the extension not the server. - _preparse_for_stopping_flags(cls, args) - # Get a jupyter server instance. - serverapp = cls.initialize_server( - argv=args, - load_other_extensions=cls.load_other_extensions + return + + # Check for help, version, and generate-config arguments + # before initializing server to make sure these + # arguments trigger actions from the extension not the server. + _preparse_for_stopping_flags(cls, args) + + serverapp = cls.initialize_server(argv=args) + + # Log if extension is blocking other extensions from loading. + if not cls.load_other_extensions: + serverapp.log.info( + "{ext_name} is running without loading " + "other extensions.".format(ext_name=cls.name) ) - # Log if extension is blocking other extensions from loading. - if not cls.load_other_extensions: - serverapp.log.info( - "{ext_name} is running without loading " - "other extensions.".format(ext_name=cls.name) - ) - try: - serverapp.start() - except NoStart: - pass + # Start the server. + try: + serverapp.start() + except NoStart: + pass \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/extension/manager.py new/jupyter_server-1.3.0/jupyter_server/extension/manager.py --- old/jupyter_server-1.2.3/jupyter_server/extension/manager.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/extension/manager.py 2021-02-05 02:40:21.000000000 +0100 @@ -1,11 +1,13 @@ import importlib -from traitlets.config import LoggingConfigurable +from traitlets.config import LoggingConfigurable, Config + from traitlets import ( HasTraits, Dict, Unicode, Bool, + Any, validate ) @@ -21,12 +23,10 @@ """A simple API for connecting to a Jupyter Server extension point defined by metadata and importable from a Python package. """ - metadata = Dict() + _linked = Bool(False) + _app = Any(None, allow_none=True) - def __init__(self, *args, **kwargs): - # Store extension points that have been linked. - self._app = None - super().__init__(*args, **kwargs) + metadata = Dict() @validate('metadata') def _valid_metadata(self, proposed): @@ -54,6 +54,13 @@ @property def linked(self): + """Has this extension point been linked to the server. + + Will pull from ExtensionApp's trait, if this point + is an instance of ExtensionApp. + """ + if self.app: + return self.app._linked return self._linked @property @@ -62,6 +69,16 @@ return self._app @property + def config(self): + """Return any configuration provided by this extension point.""" + if self.app: + return self.app._jupyter_server_config() + # At some point, we might want to add logic to load config from + # disk when extensions don't use ExtensionApp. + else: + return {} + + @property def module_name(self): """Name of the Python package module where the extension's _load_jupyter_server_extension can be found. @@ -119,8 +136,11 @@ This looks for a `_link_jupyter_server_extension` function in the extension's module or ExtensionApp class. """ - linker = self._get_linker() - return linker(serverapp) + if not self.linked: + linker = self._get_linker() + linker(serverapp) + # Store this extension as already linked. + self._linked = True def load(self, serverapp): """Load the extension in a Jupyter ServerApp object. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/gateway/managers.py new/jupyter_server-1.3.0/jupyter_server/gateway/managers.py --- old/jupyter_server-1.2.3/jupyter_server/gateway/managers.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/gateway/managers.py 2021-02-05 02:40:21.000000000 +0100 @@ -9,7 +9,7 @@ from tornado.escape import json_encode, json_decode, url_escape from tornado.httpclient import HTTPClient, AsyncHTTPClient, HTTPError -from ..services.kernels.kernelmanager import MappingKernelManager +from ..services.kernels.kernelmanager import AsyncMappingKernelManager from ..services.sessions.sessionmanager import SessionManager from jupyter_client.kernelspec import KernelSpecManager @@ -329,7 +329,7 @@ return response -class GatewayKernelManager(MappingKernelManager): +class GatewayKernelManager(AsyncMappingKernelManager): """Kernel manager that supports remote kernels hosted by Jupyter Kernel or Enterprise Gateway.""" # We'll maintain our own set of kernel ids diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/serverapp.py new/jupyter_server-1.3.0/jupyter_server/serverapp.py --- old/jupyter_server-1.2.3/jupyter_server/serverapp.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/serverapp.py 2021-02-05 02:40:21.000000000 +0100 @@ -956,25 +956,6 @@ """ ) - # The name of the app that started this server (if not started directly). - # It is sometimes important to know if + which another app (say a server extension) - # started the serverapp to properly configure some traits. - # This trait should not be configured by users. It will likely be set by ExtensionApp. - _starter_app = Instance(JupyterApp, allow_none=True) - - @validate('_starter_app') - def _validate_starter_app(self, proposal): - # Check that a previous server extension isn't named yet - value = proposal["value"] - if self._starter_app != None: - raise TraitError("Another extension was already named as the starter_server_extension.") - return value - - @property - def starter_app(self): - """Get the Extension that started this server.""" - return self._starter_app - open_browser = Bool(False, config=True, help="""Whether to open in a browser after starting. The specific browser used is platform dependent and @@ -983,31 +964,6 @@ (ServerApp.browser) configuration option. """) - - def _handle_browser_opening(self): - """This method handles whether a browser should be opened. - By default, Jupyter Server doesn't try to open an browser. However, - it's many server extensions might want to open the browser by default. - This essentially toggles the default value for open_browser. - - From a UX perspective, this needs to be surfaced to the user. The default - behavior of Jupyter Server switches, which can be confusing. - """ - # If the server was started by another application, use that applications - # trait for the open_browser trait. If that trait is not given, ignore - if self.starter_app: - try: - if self.starter_app.open_browser: - self.launch_browser() - # If the starter_app doesn't have an open_browser trait, ignore - # move on and don't start a browser. - except AttributeError: - pass - else: - if self.open_browser: - self.launch_browser() - - browser = Unicode(u'', config=True, help="""Specify what command to use to invoke a web browser when starting the server. If not specified, the @@ -1174,7 +1130,8 @@ self.root_dir = change['new'] kernel_manager_class = Type( - default_value=MappingKernelManager, + default_value=AsyncMappingKernelManager, + klass=MappingKernelManager, config=True, help=_('The kernel manager class to use.') ) @@ -1264,16 +1221,22 @@ @observe('notebook_dir') def _update_notebook_dir(self, change): + if self._root_dir_set: + # only use deprecated config if new config is not set + return self.log.warning(_("notebook_dir is deprecated, use root_dir")) self.root_dir = change['new'] - root_dir = Unicode(config=True, + root_dir = Unicode( + config=True, help=_("The directory to use for notebooks and kernels.") ) + _root_dir_set = False @default('root_dir') def _default_root_dir(self): if self.file_to_run: + self._root_dir_set = True return os.path.dirname(os.path.abspath(self.file_to_run)) else: return py3compat.getcwd() @@ -1294,6 +1257,10 @@ raise TraitError(trans.gettext("No such notebook dir: '%r'") % value) return value + @observe('root_dir') + def _root_dir_changed(self, change): + self._root_dir_set = True + @observe('server_extensions') def _update_server_extensions(self, change): self.log.warning(_("server_extensions is deprecated, use jpserver_extensions")) @@ -1351,6 +1318,17 @@ config=True ) + _starter_app = Instance( + default_value=None, + allow_none=True, + klass='jupyter_server.extension.application.ExtensionApp' + ) + + @property + def starter_app(self): + """Get the Extension that started this server.""" + return self._starter_app + def parse_command_line(self, argv=None): super(ServerApp, self).parse_command_line(argv) @@ -1781,7 +1759,7 @@ ) @catch_config_error - def initialize(self, argv=None, find_extensions=True, new_httpserver=True): + def initialize(self, argv=None, find_extensions=True, new_httpserver=True, starter_extension=None): """Initialize the Server application class, configurables, web application, and http server. Parameters @@ -1797,11 +1775,14 @@ new_httpserver: bool If True, a tornado HTTPServer instance will be created and configured for the Server Web Application. This will set the http_server attribute of this class. + + starter_extension: str + If given, it references the name of an extension point that started the Server. + We will try to load configuration from extension point """ # Parse command line, load ServerApp config files, # and update ServerApp config. - super(ServerApp, self).initialize(argv) - # Initialize all components of the ServerApp. + super(ServerApp, self).initialize(argv=argv) if self._dispatching: return # Then, use extensions' config loading mechanism to @@ -1810,6 +1791,19 @@ self.find_server_extensions() self.init_logging() self.init_server_extensions() + + # Special case the starter extension and load + # any server configuration is provides. + if starter_extension: + # Configure ServerApp based on named extension. + point = self.extension_manager.extension_points[starter_extension] + # Set starter_app property. + if point.app: + self._starter_app = point.app + # Load any configuration that comes from the Extension point. + self.update_config(Config(point.config)) + + # Initialize other pieces of the server. self.init_resources() self.init_configurables() self.init_components() @@ -1979,7 +1973,8 @@ self.write_browser_open_file() # Handle the browser opening. - self._handle_browser_opening() + if self.open_browser: + self.launch_browser() if self.token and self._token_generated: # log full URL with generated token, so there's a copy/pasteable link diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/jupyter_server/utils.py new/jupyter_server-1.3.0/jupyter_server/utils.py --- old/jupyter_server-1.2.3/jupyter_server/utils.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/jupyter_server/utils.py 2021-02-05 02:40:21.000000000 +0100 @@ -225,4 +225,4 @@ # just return a Future, hoping that it will be awaited result = asyncio.ensure_future(maybe_async) return result - return wrapped() + return wrapped() \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/tests/extension/mockextensions/app.py new/jupyter_server-1.3.0/tests/extension/mockextensions/app.py --- old/jupyter_server-1.2.3/tests/extension/mockextensions/app.py 2021-01-30 00:34:24.000000000 +0100 +++ new/jupyter_server-1.3.0/tests/extension/mockextensions/app.py 2021-02-05 02:40:21.000000000 +0100 @@ -13,6 +13,15 @@ STATIC_PATH = os.path.join(os.path.dirname(__file__), "static") +# Function that makes these extensions discoverable +# by the test functions. +def _jupyter_server_extension_points(): + return [ + { + 'module': __name__, + 'app': MockExtensionApp + } + ] class MockExtensionHandler(ExtensionHandlerMixin, JupyterHandler): @@ -41,4 +50,8 @@ def initialize_handlers(self): self.handlers.append(('/mock', MockExtensionHandler)) self.handlers.append(('/mock_template', MockExtensionTemplateHandler)) - self.loaded = True \ No newline at end of file + self.loaded = True + + +if __name__ == "__main__": + MockExtensionApp.launch_instance() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server-1.2.3/tests/extension/test_launch.py new/jupyter_server-1.3.0/tests/extension/test_launch.py --- old/jupyter_server-1.2.3/tests/extension/test_launch.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jupyter_server-1.3.0/tests/extension/test_launch.py 2021-02-05 02:40:21.000000000 +0100 @@ -0,0 +1,95 @@ +"""Test launching Jupyter Server Applications +through as ExtensionApp launch_instance. +""" +import os +import sys +import time +import pytest +import subprocess +import requests +from binascii import hexlify + + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +@pytest.fixture +def port(): + return 9999 + + +@pytest.fixture +def token(): + return hexlify(os.urandom(4)).decode("ascii") + + +@pytest.fixture +def auth_header(token): + return { + 'Authorization': 'token %s' % token + } + + +def wait_up(url, interval=0.1, check=None): + while True: + try: + r = requests.get(url) + except Exception: + if check: + assert check() + #print("waiting for %s" % url) + time.sleep(interval) + else: + break + + +@pytest.fixture +def launch_instance(request, port, token): + def _run_in_subprocess(argv=[]): + + def _kill_extension_app(): + try: + process.terminate() + except OSError: + # Already dead. + pass + process.wait(10) + + process = subprocess.Popen([ + sys.executable, '-m', + 'mockextensions.app', + f'--port={port}', + '--ip=127.0.0.1', + '--no-browser', + f'--ServerApp.token={token}', + *argv, + ], cwd=HERE) + + request.addfinalizer(_kill_extension_app) + url = f'http://127.0.0.1:{port}' + wait_up(url, check=lambda: process.poll() is None) + return process + + return _run_in_subprocess + + +@pytest.fixture +def fetch(port, auth_header): + def _get(endpoint): + url = f"http://127.0.0.1:{port}" + endpoint + return requests.get(url, headers=auth_header) + return _get + + +def test_launch_instance(launch_instance, fetch): + launch_instance() + r = fetch('/mock') + assert r.status_code == 200 + + +def test_base_url(launch_instance, fetch): + launch_instance(['--ServerApp.base_url=/foo']) + r = fetch("/foo/mock") + assert r.status_code == 200 + +