You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""Extension utilities."""
|
|
|
|
import importlib
|
|
import time
|
|
import warnings
|
|
|
|
|
|
class ExtensionLoadingError(Exception):
|
|
"""An extension loading error."""
|
|
|
|
|
|
class ExtensionMetadataError(Exception):
|
|
"""An extension metadata error."""
|
|
|
|
|
|
class ExtensionModuleNotFound(Exception):
|
|
"""An extension module not found error."""
|
|
|
|
|
|
class NotAnExtensionApp(Exception):
|
|
"""An error raised when a module is not an extension."""
|
|
|
|
|
|
def get_loader(obj, logger=None):
|
|
"""Looks for _load_jupyter_server_extension as an attribute
|
|
of the object or module.
|
|
|
|
Adds backwards compatibility for old function name missing the
|
|
underscore prefix.
|
|
"""
|
|
try:
|
|
return obj._load_jupyter_server_extension
|
|
except AttributeError:
|
|
pass
|
|
|
|
try:
|
|
func = obj.load_jupyter_server_extension
|
|
except AttributeError:
|
|
msg = "_load_jupyter_server_extension function was not found."
|
|
raise ExtensionLoadingError(msg) from None
|
|
|
|
warnings.warn(
|
|
"A `_load_jupyter_server_extension` function was not "
|
|
f"found in {obj!s}. Instead, a `load_jupyter_server_extension` "
|
|
"function was found and will be used for now. This function "
|
|
"name will be deprecated in future releases "
|
|
"of Jupyter Server.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
return func
|
|
|
|
|
|
def get_metadata(package_name, logger=None):
|
|
"""Find the extension metadata from an extension package.
|
|
|
|
This looks for a `_jupyter_server_extension_points` function
|
|
that returns metadata about all extension points within a Jupyter
|
|
Server Extension package.
|
|
|
|
If it doesn't exist, return a basic metadata packet given
|
|
the module name.
|
|
"""
|
|
start_time = time.perf_counter()
|
|
module = importlib.import_module(package_name)
|
|
end_time = time.perf_counter()
|
|
duration = end_time - start_time
|
|
# Sometimes packages can take a *while* to import, so we report how long
|
|
# each module took to import. This makes it much easier for users to report
|
|
# slow loading modules upstream, as slow loading modules will block server startup
|
|
if logger:
|
|
log = logger.info if duration > 0.1 else logger.debug
|
|
log(f"Extension package {package_name} took {duration:.4f}s to import")
|
|
|
|
try:
|
|
return module, module._jupyter_server_extension_points()
|
|
except AttributeError:
|
|
pass
|
|
|
|
# For backwards compatibility, we temporarily allow
|
|
# _jupyter_server_extension_paths. We will remove in
|
|
# a later release of Jupyter Server.
|
|
try:
|
|
extension_points = module._jupyter_server_extension_paths()
|
|
if logger:
|
|
logger.warning(
|
|
"A `_jupyter_server_extension_points` function was not "
|
|
f"found in {package_name}. Instead, a `_jupyter_server_extension_paths` "
|
|
"function was found and will be used for now. This function "
|
|
"name will be deprecated in future releases "
|
|
"of Jupyter Server."
|
|
)
|
|
return module, extension_points
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Dynamically create metadata if the package doesn't
|
|
# provide it.
|
|
if logger:
|
|
logger.debug(
|
|
"A `_jupyter_server_extension_points` function was "
|
|
f"not found in {package_name}, so Jupyter Server will look "
|
|
"for extension points in the extension pacakge's "
|
|
"root."
|
|
)
|
|
return module, [{"module": package_name, "name": package_name}]
|
|
|
|
|
|
def validate_extension(name):
|
|
"""Raises an exception is the extension is missing a needed
|
|
hook or metadata field.
|
|
An extension is valid if:
|
|
1) name is an importable Python package.
|
|
1) the package has a _jupyter_server_extension_points function
|
|
2) each extension path has a _load_jupyter_server_extension function
|
|
|
|
If this works, nothing should happen.
|
|
"""
|
|
from .manager import ExtensionPackage
|
|
|
|
return ExtensionPackage(name=name)
|