diff --git a/docs/source/developers/extensions.rst b/docs/source/developers/extensions.rst index 7e41e4716b..b3c37c11d9 100644 --- a/docs/source/developers/extensions.rst +++ b/docs/source/developers/extensions.rst @@ -109,9 +109,9 @@ An ExtensionApp: - has traits. - is configurable (from file or CLI) - - has a name (see the ``extension_name`` trait). - - has an entrypoint, ``jupyter ``. - - can serve static content from the ``/static//`` endpoint. + - has a name (see the ``name`` trait). + - has an entrypoint, ``jupyter ``. + - can serve static content from the ``/static//`` endpoint. - can add new endpoints to the Jupyter Server. The basic structure of an ExtensionApp is shown below: @@ -124,7 +124,7 @@ The basic structure of an ExtensionApp is shown below: class MyExtensionApp(ExtensionApp): # -------------- Required traits -------------- - extension_name = "myextension" + name = "myextension" extension_url = "/myextension" load_other_extensions = True @@ -163,7 +163,7 @@ Methods Properties -* ``extension_name``: the name of the extension +* ``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. * ``load_other_extensions``: a boolean enabling/disabling other extensions when launching this extension directly. @@ -174,8 +174,8 @@ Properties * ``config``: the ExtensionApp's config object. * ``server_config``: the ServerApp's config object. -* ``extension_name``: the name of the extension to which this handler is linked. -* ``static_url()``: a method that returns the url to static files (prefixed with ``/static/``). +* ``name``: the name of the extension to which this handler is linked. +* ``static_url()``: a method that returns the url to static files (prefixed with ``/static/``). Jupyter Server provides a convenient mixin class for adding these properties to any ``JupyterHandler``. For example, the basic server extension handler in the section above becomes: @@ -202,7 +202,7 @@ Jinja templating from frontend extensions Many Jupyter frontend applications use Jinja for basic HTML templating. Since this is common enough, Jupyter Server provides some extra mixin that integrate Jinja with Jupyter server extensions. -Use ``ExtensionAppJinjaMixin`` to automatically add a Jinja templating environment to an ``ExtensionApp``. This adds a ``_jinja2_env`` setting to Tornado Web Server's settings that will be used by request handlers. +Use ``ExtensionAppJinjaMixin`` to automatically add a Jinja templating environment to an ``ExtensionApp``. This adds a ``_jinja2_env`` setting to Tornado Web Server's settings that will be used by request handlers. .. code-block:: python diff --git a/docs/source/operators/configuring-extensions.rst b/docs/source/operators/configuring-extensions.rst index ac564f85c8..0c8cba3d4a 100644 --- a/docs/source/operators/configuring-extensions.rst +++ b/docs/source/operators/configuring-extensions.rst @@ -26,7 +26,7 @@ Jupyter Server looks for an extension's config file in a set of specific paths. Extension config from file -------------------------- -Jupyter Server expects the file to be named after the extension's name like so: ``jupyter_{extension_name}_config``. For example, the Jupyter Notebook's config file is ``jupyter_notebook_config``. +Jupyter Server expects the file to be named after the extension's name like so: ``jupyter_{name}_config``. For example, the Jupyter Notebook's config file is ``jupyter_notebook_config``. Configuration files can be Python or JSON files. diff --git a/examples/simple/simple_ext1/application.py b/examples/simple/simple_ext1/application.py index 20a9aebc75..4ddf889096 100644 --- a/examples/simple/simple_ext1/application.py +++ b/examples/simple/simple_ext1/application.py @@ -10,7 +10,7 @@ class SimpleApp1(ExtensionAppJinjaMixin, ExtensionApp): # The name of the extension. - extension_name = "simple_ext1" + name = "simple_ext1" # The url that your extension will serve its homepage. extension_url = '/simple_ext1/default' @@ -45,11 +45,11 @@ class SimpleApp1(ExtensionAppJinjaMixin, ExtensionApp): def initialize_handlers(self): self.handlers.extend([ - (r'/{}/default'.format(self.extension_name), DefaultHandler), - (r'/{}/params/(.+)$'.format(self.extension_name), ParameterHandler), - (r'/{}/template1/(.*)$'.format(self.extension_name), TemplateHandler), - (r'/{}/redirect'.format(self.extension_name), RedirectHandler), - (r'/{}/typescript/?'.format(self.extension_name), TypescriptHandler), + (r'/{}/default'.format(self.name), DefaultHandler), + (r'/{}/params/(.+)$'.format(self.name), ParameterHandler), + (r'/{}/template1/(.*)$'.format(self.name), TemplateHandler), + (r'/{}/redirect'.format(self.name), RedirectHandler), + (r'/{}/typescript/?'.format(self.name), TypescriptHandler), (r'/{}/(.*)', ErrorHandler) ]) diff --git a/examples/simple/simple_ext1/handlers.py b/examples/simple/simple_ext1/handlers.py index f635d1e2c1..fd7fef4405 100644 --- a/examples/simple/simple_ext1/handlers.py +++ b/examples/simple/simple_ext1/handlers.py @@ -4,17 +4,17 @@ class DefaultHandler(ExtensionHandlerMixin, JupyterHandler): def get(self): # The name of the extension to which this handler is linked. - self.log.info("Extension Name in {} Default Handler: {}".format(self.extension_name, self.extension_name)) - # A method for getting the url to static files (prefixed with /static/). + self.log.info("Extension Name in {} Default Handler: {}".format(self.name, self.name)) + # A method for getting the url to static files (prefixed with /static/). self.log.info("Static URL for / in simple_ext1 Default Handler:".format(self.static_url(path='/'))) self.write('

Hello Simple 1 - I am the default...

') - self.write('Config in {} Default Handler: {}'.format(self.extension_name, self.config)) + self.write('Config in {} Default Handler: {}'.format(self.name, self.config)) class RedirectHandler(ExtensionHandlerMixin, JupyterHandler): def get(self): - self.redirect("/static/{}/favicon.ico".format(self.extension_name)) + self.redirect("/static/{}/favicon.ico".format(self.name)) -class ParameterHandler(ExtensionHandlerMixin, JupyterHandler): +class ParameterHandler(ExtensionHandlerMixin, JupyterHandler): def get(self, matched_part=None, *args, **kwargs): var1 = self.get_argument('var1', default=None) components = [x for x in self.request.path.split("/") if x] diff --git a/examples/simple/simple_ext11/application.py b/examples/simple/simple_ext11/application.py index bc04a16a5c..b772bb34ff 100644 --- a/examples/simple/simple_ext11/application.py +++ b/examples/simple/simple_ext11/application.py @@ -15,7 +15,7 @@ class SimpleApp11(SimpleApp1): }) # The name of the extension. - extension_name = "simple_ext11" + name = "simple_ext11" # Te url that your extension will serve its homepage. extension_url = '/simple_ext11/default' diff --git a/examples/simple/simple_ext2/application.py b/examples/simple/simple_ext2/application.py index 7251da7900..be59ce9bfa 100644 --- a/examples/simple/simple_ext2/application.py +++ b/examples/simple/simple_ext2/application.py @@ -9,7 +9,7 @@ class SimpleApp2(ExtensionAppJinjaMixin, ExtensionApp): # The name of the extension. - extension_name = "simple_ext2" + name = "simple_ext2" # Te url that your extension will serve its homepage. extension_url = '/simple_ext2' diff --git a/jupyter_server/extension/application.py b/jupyter_server/extension/application.py index f3ce62e59d..6a30d6ee8a 100644 --- a/jupyter_server/extension/application.py +++ b/jupyter_server/extension/application.py @@ -96,7 +96,7 @@ def _prepare_templates(self): # Add templates to web app settings if extension has templates. if len(self.template_paths) > 0: self.settings.update({ - "{}_template_paths".format(self.extension_name): self.template_paths + "{}_template_paths".format(self.name): self.template_paths }) # Create a jinja environment for logging html templates. @@ -111,7 +111,7 @@ def _prepare_templates(self): # Add the jinja2 environment for this extension to the tornado settings. self.settings.update( { - "{}_jinja2_env".format(self.extension_name): self.jinja2_env + "{}_jinja2_env".format(self.name): self.jinja2_env } ) @@ -144,32 +144,11 @@ class method. This method can be set as a entry_point in # side-by-side when launched directly. load_other_extensions = True - # Name of the extension - extension_name = Unicode( - help="Name of extension." - ) - - @default('extension_name') - def _extension_name_default(self): - try: - return self.name - except AttributeError: - raise ValueError("The extension must be given a `name`.") - - INVALID_EXTENSION_NAME_CHARS = [' ', '.', '+', '/'] - - @validate('extension_name') - def _validate_extension_name(self, proposal): - value = proposal['value'] - if isinstance(value, str): - # Validate that extension_name doesn't contain any invalid characters. - for c in ExtensionApp.INVALID_EXTENSION_NAME_CHARS: - if c in value: - raise ValueError("Extension name '{name}' cannot contain any of the following characters: " - "{invalid_chars}.". - format(name=value, invalid_chars=ExtensionApp.INVALID_EXTENSION_NAME_CHARS)) - return value - raise ValueError("Extension name must be a string, found {type}.".format(type=type(value))) + # The extension name used to name the jupyter config + # file, jupyter_{name}_config. + # This should also match the jupyter subcommand used to launch + # this extension from the CLI, e.g. `jupyter {name}`. + name = None # Extension URL sets the default landing page for this extension. extension_url = "/" @@ -181,8 +160,8 @@ def _validate_extension_name(self, proposal): @property def static_url_prefix(self): - return "/static/{extension_name}/".format( - extension_name=self.extension_name) + return "/static/{name}/".format( + name=self.name) static_paths = List(Unicode(), help="""paths to search for serving static files. @@ -216,10 +195,9 @@ def static_url_prefix(self): def _config_file_name_default(self): """The default config file name.""" - if not self.extension_name: + if not self.name: return '' - return 'jupyter_{}_config'.format(self.extension_name.replace('-','_')) - + return 'jupyter_{}_config'.format(self.name.replace('-','_')) def initialize_settings(self): """Override this method to add handling of settings.""" @@ -235,11 +213,11 @@ def initialize_templates(self): def _prepare_config(self): """Builds a Config object from the extension's traits and passes - the object to the webapp's settings as `_config`. + the object to the webapp's settings as `_config`. """ traits = self.class_own_traits().keys() self.extension_config = Config({t: getattr(self, t) for t in traits}) - self.settings['{}_config'.format(self.extension_name)] = self.extension_config + self.settings['{}_config'.format(self.name)] = self.extension_config def _prepare_settings(self): # Make webapp settings accessible to initialize_settings method @@ -248,8 +226,8 @@ def _prepare_settings(self): # Add static and template paths to settings. self.settings.update({ - "{}_static_paths".format(self.extension_name): self.static_paths, - "{}".format(self.extension_name): self + "{}_static_paths".format(self.name): self.static_paths, + "{}".format(self.name): self }) # Get setting defined by subclass using initialize_settings method. @@ -274,7 +252,8 @@ def _prepare_handlers(self): # Get handler kwargs, if given kwargs = {} if issubclass(handler, ExtensionHandlerMixin): - kwargs['extension_name'] = self.extension_name + kwargs['name'] = self.name + try: kwargs.update(handler_items[2]) except IndexError: @@ -302,7 +281,7 @@ def _prepare_templates(self): # Add templates to web app settings if extension has templates. if len(self.template_paths) > 0: self.settings.update({ - "{}_template_paths".format(self.extension_name): self.template_paths + "{}_template_paths".format(self.name): self.template_paths }) self.initialize_templates() @@ -319,7 +298,7 @@ def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs): # initializes it. config = Config({ "ServerApp": { - "jpserver_extensions": {cls.extension_name: True}, + "jpserver_extensions": {cls.name: True}, "open_browser": cls.open_browser, "default_url": cls.extension_url } @@ -402,7 +381,7 @@ def _load_jupyter_server_extension(cls, serverapp): """ try: # Get loaded extension from serverapp. - extension = serverapp._enabled_extensions[cls.extension_name] + extension = serverapp._enabled_extensions[cls.name] except KeyError: extension = cls() extension.link_to_serverapp(serverapp) @@ -438,6 +417,6 @@ def launch_instance(cls, argv=None, **kwargs): if not cls.load_other_extensions: serverapp.log.info( "{ext_name} is running without loading " - "other extensions.".format(ext_name=cls.extension_name) + "other extensions.".format(ext_name=cls.name) ) serverapp.start() \ No newline at end of file diff --git a/jupyter_server/extension/handler.py b/jupyter_server/extension/handler.py index c60c566fa0..d0c33e87c1 100644 --- a/jupyter_server/extension/handler.py +++ b/jupyter_server/extension/handler.py @@ -8,7 +8,7 @@ class ExtensionHandlerJinjaMixin: """ def get_template(self, name): """Return the jinja template object for a given name""" - env = '{}_jinja2_env'.format(self.extension_name) + env = '{}_jinja2_env'.format(self.name) return self.settings[env].get_template(name) @@ -16,18 +16,18 @@ class ExtensionHandlerMixin: """Base class for Jupyter server extension handlers. Subclasses can serve static files behind a namespaced - endpoint: "/static//" + endpoint: "/static//" This allows multiple extensions to serve static files under their own namespace and avoid intercepting requests for other extensions. """ - def initialize(self, extension_name): - self.extension_name = extension_name + def initialize(self, name): + self.name = name @property def extensionapp(self): - return self.settings[self.extension_name] + return self.settings[self.name] @property def serverapp(self): @@ -36,7 +36,7 @@ def serverapp(self): @property def config(self): - return self.settings["{}_config".format(self.extension_name)] + return self.settings["{}_config".format(self.name)] @property def server_config(self): @@ -44,16 +44,15 @@ def server_config(self): @property def static_url_prefix(self): - return "/static/{extension_name}/".format( - extension_name=self.extension_name) + return "/static/{name}/".format(name=self.name) @property def static_path(self): - return self.settings['{}_static_paths'.format(self.extension_name)] + return self.settings['{}_static_paths'.format(self.name)] def static_url(self, path, include_host=None, **kwargs): """Returns a static URL for the given relative static file path. - This method requires you set the ``{extension_name}_static_path`` + This method requires you set the ``{name}_static_path`` setting in your extension (which specifies the root directory of your static files). This method returns a versioned url (by default appending @@ -68,7 +67,7 @@ def static_url(self, path, include_host=None, **kwargs): that value will be used as the default for all `static_url` calls that do not pass ``include_host`` as a keyword argument. """ - key = "{}_static_paths".format(self.extension_name) + key = "{}_static_paths".format(self.name) try: self.require_setting(key, "static_url") except Exception as e: diff --git a/jupyter_server/extension/serverextension.py b/jupyter_server/extension/serverextension.py index 755920c53f..3c34c44811 100644 --- a/jupyter_server/extension/serverextension.py +++ b/jupyter_server/extension/serverextension.py @@ -154,7 +154,7 @@ def _get_load_jupyter_server_extension(obj): return func -def validate_server_extension(extension_name): +def validate_server_extension(name): """Validates that you can import the extension module, gather all extension metadata, and find `load_jupyter_server_extension` functions for each extension. @@ -176,10 +176,10 @@ def validate_server_extension(extension_name): """ # If the extension does not exist, raise an exception try: - mod, metadata = _get_server_extension_metadata(extension_name) + mod, metadata = _get_server_extension_metadata(name) version = getattr(mod, '__version__', '') except ImportError: - raise ExtensionValidationError('{} is not importable.'.format(extension_name)) + raise ExtensionValidationError('{} is not importable.'.format(name)) try: for item in metadata: @@ -194,7 +194,7 @@ def validate_server_extension(extension_name): raise AttributeError # If the extension does not have a `load_jupyter_server_extension` function, raise exception. except AttributeError: - raise ExtensionValidationError('Found "{}" module but cannot load it.'.format(extension_name)) + raise ExtensionValidationError('Found "{}" module but cannot load it.'.format(name)) return version diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 21a4e68ca9..0822bd7cc1 100755 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -1571,7 +1571,7 @@ def init_server_extensions(self): app = extapp() app.link_to_serverapp(self) # Build a new list where we - self._enabled_extensions[app.extension_name] = app + self._enabled_extensions[app.name] = app elif extloc: extmod = importlib.import_module(extloc) func = _get_load_jupyter_server_extension(extmod) @@ -1603,8 +1603,8 @@ def load_server_extensions(self): ) else: log_msg = ( - "Extension {extension_name} enabled and " - "loaded".format(extension_name=extension.extension_name) + "Extension {name} enabled and " + "loaded".format(name=extension.name) ) # Find the extension loading function. func = None diff --git a/tests/extension/mockextensions/app.py b/tests/extension/mockextensions/app.py index 06a5cc0fd0..fac5a5f957 100644 --- a/tests/extension/mockextensions/app.py +++ b/tests/extension/mockextensions/app.py @@ -18,10 +18,10 @@ def get(self): class MockExtensionTemplateHandler( - ExtensionHandlerJinjaMixin, - ExtensionHandlerMixin, - JupyterHandler - ): + ExtensionHandlerJinjaMixin, + ExtensionHandlerMixin, + JupyterHandler +): def get(self): self.write(self.render_template("index.html")) @@ -29,7 +29,7 @@ def get(self): class MockExtensionApp(ExtensionAppJinjaMixin, ExtensionApp): - extension_name = 'mockextension' + name = 'mockextension' template_paths = List().tag(config=True) mock_trait = Unicode('mock trait', config=True) loaded = False