|
"""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 |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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) |
|
|