예제 #1
0
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]
예제 #2
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")
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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()
예제 #6
0
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)
예제 #7
0
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__))
예제 #8
0
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)