#!/usr/bin/env python3 """Copy bundle extension files to the client/app/extension directory""" import logging import os from pathlib import Path from shutil import copy from collections import OrderedDict as odict from importlib_metadata import entry_points from importlib_resources import contents, is_resource, path # Name of the subdirectory BUNDLE_DIRECTORY = "bundle" logger = logging.getLogger(__name__) # Make a directory for extensions and set it as an environment variable # to be picked up by webpack. extensions_relative_path = Path('client', 'app', 'extensions') extensions_directory = Path(__file__).parent.parent / extensions_relative_path if not extensions_directory.exists(): extensions_directory.mkdir() os.environ["EXTENSIONS_DIRECTORY"] = str(extensions_relative_path) def resource_isdir(module, resource): """Whether a given resource is a directory in the given module https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-isdir """ try: return resource in contents(module) and not is_resource(module, resource) except (ImportError, TypeError): # module isn't a package, so can't have a subdirectory/-package return False def entry_point_module(entry_point): """Returns the dotted module path for the given entry point""" return entry_point.pattern.match(entry_point.value).group("module") def load_bundles(): """"Load bundles as defined in Redash extensions. The bundle entry point can be defined as a dotted path to a module or a callable, but it won't be called but just used as a means to find the files under its file system path. The name of the directory it looks for files in is "bundle". So a Python package with an extension bundle could look like this:: my_extensions/ ├── __init__.py └── wide_footer ├── __init__.py └── bundle ├── extension.js └── styles.css and would then need to register the bundle with an entry point under the "redash.bundles" group, e.g. in your setup.py:: setup( # ... entry_points={ "redash.bundles": [ "wide_footer = my_extensions.wide_footer", ] # ... }, # ... ) """ bundles = odict() for entry_point in entry_points().get("redash.bundles", []): logger.info('Loading Redash bundle "%s".', entry_point.name) module = entry_point_module(entry_point) # Try to get a list of bundle files if not resource_isdir(module, BUNDLE_DIRECTORY): logger.error( 'Redash bundle directory "%s" could not be found.', entry_point.name ) continue with path(module, BUNDLE_DIRECTORY) as bundle_dir: bundles[entry_point.name] = list(bundle_dir.rglob("*")) return bundles bundles = load_bundles().items() if bundles: print('Number of extension bundles found: {}'.format(len(bundles))) else: print('No extension bundles found.') for bundle_name, paths in bundles: # Shortcut in case not paths were found for the bundle if not paths: print('No paths found for bundle "{}".'.format(bundle_name)) continue # The destination for the bundle files with the entry point name as the subdirectory destination = Path(extensions_directory, bundle_name) if not destination.exists(): destination.mkdir() # Copy the bundle directory from the module to its destination. print('Copying "{}" bundle to {}:'.format(bundle_name, destination.resolve())) for src_path in paths: dest_path = destination / src_path.name print(" - {} -> {}".format(src_path, dest_path)) copy(str(src_path), str(dest_path))