def get_importname_from_package(package_name: str) -> str: """ Try to fetch the name of the top-level import name for the given package. For some reason, this isn't straightforward. For now, we do not support distribution packages that contains multiple top-level packages. """ try: dist = importlib_metadata.distribution(package_name) except importlib_metadata.PackageNotFoundError: raise DiscoveryFailed("Package {p} not found ".format(p=package_name)) try: packages = dist.top_level except FileNotFoundError: raise DiscoveryFailed( "failed to load package {p} metadata. " "Was the package installed properly?".format(p=package_name)) if len(packages) > 1: raise DiscoveryFailed( "Package {p} contains multiple top-level packages. " "Unable to discover from multiple packages.".format( p=package_name)) return packages[0]
def install(package_name: str): """ Use pip to download and install the `package_name` to the current Python environment. Pip can detect it is already installed. """ logger.info(f"Attempting to download and install package '{package_name}'") process = subprocess.run( ["pip", "install", "-U", package_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout = process.stdout.decode("utf-8") stderr = process.stderr.decode("utf-8") logger.debug(stdout) if process.returncode != 0: msg = f"failed to install `{package_name}`" logger.debug(msg + "\n=================\n{o}\n=================\n{e}\n". format(o=stdout, e=stderr)) raise DiscoveryFailed(msg) logger.info("Package downloaded and installed in current environment")
def discover_activities( extension_mod_name: str, activity_type: str # noqa: C901 ) -> DiscoveredActivities: """ Discover exported activities from the given extension module name. """ try: mod = importlib.import_module(extension_mod_name) except ImportError: raise DiscoveryFailed( f"could not import extension module '{extension_mod_name}'" ) activities = [] try: exported = getattr(mod, "__all__") except AttributeError: logger.warning( "'{m}' does not expose the __all__ attribute. " "It is required to determine what functions are actually " "exported as activities.".format(m=extension_mod_name) ) return activities funcs = inspect.getmembers(mod, inspect.isfunction) for (name, func) in funcs: if exported and name not in exported: # do not return "private" functions continue sig = inspect.signature(func) activity = { "type": activity_type, "name": name, "mod": mod.__name__, "doc": inspect.getdoc(func), "arguments": [], } if sig.return_annotation is not inspect.Signature.empty: activity["return_type"] = portable_type_name(sig.return_annotation) for param in sig.parameters.values(): if param.kind in (param.KEYWORD_ONLY, param.VAR_KEYWORD): continue arg = { "name": param.name, } if param.default is not inspect.Parameter.empty: arg["default"] = param.default if param.annotation is not inspect.Parameter.empty: arg["type"] = portable_type_name(param.annotation) activity["arguments"].append(arg) activities.append(activity) return activities
def load_package(package_name: str) -> object: """ Import the module into the current process state. """ name = get_importname_from_package(package_name) try: package = importlib.import_module(name) except ImportError: raise DiscoveryFailed(f"could not load Python module '{name}'") return package
def get_importname_from_package(package_name: str) -> str: """ Try to fetch the name of the top-level import name for the given package. For some reason, this isn't straightforward. """ reqs = list(pkg_resources.parse_requirements(package_name)) if not reqs: raise DiscoveryFailed( "no requirements met for package '{p}'".format(p=package_name)) req = reqs[0] dist = pkg_resources.get_distribution(req) try: name = dist.get_metadata('top_level.txt').split("\n)", 1)[0] except FileNotFoundError: raise DiscoveryFailed( "failed to load package '{p}' metadata. " "Was the package installed properly?".format(p=package_name)) return name.strip()
def test_notify_discover_complete(disco, notify): err = DiscoveryFailed() disco.side_effect = err runner = CliRunner() result = runner.invoke(cli, [ 'discover', '--no-install', 'chaostoolkit-kubernetes']) assert result.exit_code == 0 assert result.exception is None notify.assert_any_call(ANY, DiscoverFlowEvent.DiscoverStarted, ANY) notify.assert_called_with(ANY, DiscoverFlowEvent.DiscoverFailed, ANY, err)
def get_discover_function(package: object): """ Lookup the `discover` function from the given imported package. """ funcs = inspect.getmembers(package, inspect.isfunction) for (name, value) in funcs: if name == 'discover': return value raise DiscoveryFailed( "package '{name}' does not export a `discover` function".format( name=package.__name__))
def test_notify_discover_complete(disco, notify): err = DiscoveryFailed() disco.side_effect = err runner = CliRunner() result = runner.invoke( cli, [ "--settings", empty_settings_path, "discover", "--no-install", "chaostoolkit-kubernetes", ], ) assert result.exit_code == 0 assert result.exception is None notify.assert_any_call(ANY, DiscoverFlowEvent.DiscoverStarted, ANY) notify.assert_called_with(ANY, DiscoverFlowEvent.DiscoverFailed, ANY, err)