Ejemplo n.º 1
0
def search_license_api_github(
        github_url: str,
        version: Optional[str] = None,
        default: Optional[str] = "Other") -> Optional[ShortLicense]:
    """Search for the license asking in the github api

    :param github_url: GitHub URL
    :param version: Package version
    :param default: default license type
    :return: License information
    """
    if github_url.endswith("/"):
        github_url = github_url[:-1]
    github_url = _get_api_github_url(github_url, version)
    log.info(f"Github url: {github_url} - recovering license info")
    print_msg("Recovering license information from github...")

    response = requests.get(url=github_url, timeout=10)
    if response.status_code != 200:
        return None

    json_content = response.json()
    license_content = base64.b64decode(json_content["content"]).decode("utf-8")
    file_path = os.path.join(mkdtemp(prefix="github-license-"),
                             json_content["name"])
    with open(file_path, "w") as f:
        f.write(license_content)
    return ShortLicense(
        json_content.get("license", {}).get("spdx_id", default), file_path,
        False)
Ejemplo n.º 2
0
def search_license_repo(
        git_url: str,
        version: Optional[str],
        default: Optional[str] = None) -> Optional[ShortLicense]:
    """Search for the license file in the given github repository

    :param git_url: GitHub URL
    :param version: Package version
    :param default: Default value for the license type
    :return: License information
    """
    git_url = re.sub(r"/$", ".git", git_url)
    git_url = git_url if git_url.endswith(".git") else f"{git_url}.git"
    print_msg("Recovering license info from repository...")
    tmp_dir = mkdtemp(prefix="gs-clone-repo-")
    try:
        check_output(_get_git_cmd(git_url, version, tmp_dir))
    except Exception as err:
        log.debug(
            f"Exception occurred when gs was trying to clone the repository."
            f" url: {git_url}, version: {version}. Exception: {err}")
        if not version.startswith("v"):
            return search_license_repo(git_url, f"v{version}", default)
        return None
    return search_license_folder(str(tmp_dir), default)
Ejemplo n.º 3
0
    def _get_setup_cfg(source_path: str) -> dict:
        """Method responsible to extract the setup.cfg metadata

        :param source_path: Path to the folder where is located the sdist
         files unpacked
        :return: Metadata of setup.cfg
        """
        from setuptools.config import read_configuration

        log.debug(f"Started setup.cfg from {source_path}")
        print_msg("Recovering metadata from setup.cfg")
        path_setup_cfg = list(Path(source_path).rglob("setup.cfg"))
        if not path_setup_cfg:
            return {}
        path_setup_cfg = path_setup_cfg[0]

        setup_cfg = read_configuration(str(path_setup_cfg))
        setup_cfg = dict(setup_cfg)
        if setup_cfg.get("options", {}).get("python_requires"):
            setup_cfg["options"]["python_requires"] = str(
                setup_cfg["options"]["python_requires"])
        result = {}
        result.update(setup_cfg.get("options", {}))
        result.update(setup_cfg.get("metadata", {}))
        if result.get("build_ext"):
            result["compilers"] = ["c"]
        log.debug(f"Data recovered from setup.cfg: {result}")
        return result
Ejemplo n.º 4
0
    def _download_sdist_pkg(sdist_url: str, dest: str):
        """Download the sdist package

        :param sdist_url: sdist url
        :param dest: Folder were the method will download the sdist
        """
        name = sdist_url.split("/")[-1]
        print_msg(f"{Fore.GREEN}Starting the download of the sdist package"
                  f" {Fore.BLUE}{Style.BRIGHT}{name}")
        log.debug(f"Downloading {name} sdist - {sdist_url}")
        response = requests.get(sdist_url,
                                allow_redirects=True,
                                stream=True,
                                timeout=5)
        total_size = int(response.headers["Content-length"])

        with manage_progressbar(max_value=total_size,
                                prefix=f"{name} ") as bar, open(
                                    dest, "wb") as pkg_file:
            progress_val = 0
            chunk_size = 512
            for chunk_data in response.iter_content(chunk_size=chunk_size):
                if chunk_data:
                    pkg_file.write(chunk_data)
                    progress_val += chunk_size
                    bar.update(min(progress_val, total_size))
Ejemplo n.º 5
0
def get_all_licenses_from_spdx() -> List:
    """Get all licenses available on spdx.org

    :return: List with all licenses information on spdx.org
    """
    try:
        response = requests.get(
            url="https://spdx.org/licenses/licenses.json", timeout=5
        )
    except requests.exceptions.ConnectionError:
        log.info(
            "SPDX licence server didn't respond. Grayskull will continue without that."
        )
        return []
    log.debug(
        f"Response from spdx.org. Status code:{response.status_code},"
        f" response: {response}"
    )
    if response.status_code != 200:
        raise requests.HTTPError(
            f"It was not possible to communicate with spdx api.\n{response.text}"
        )
    print_msg("Recovering license info from spdx.org ...")
    return [
        lic
        for lic in response.json()["licenses"]
        if not lic.get("isDeprecatedLicenseId", False)
    ]
Ejemplo n.º 6
0
 def _add_extra_section(self, maintainers: Optional[List] = None):
     if not self["extra"]:
         maintainers = maintainers if maintainers else [
             get_git_current_user()
         ]
         self["extra"]["recipe-maintainers"].add_items(maintainers)
     prefix = f"\n   - {Fore.LIGHTMAGENTA_EX}"
     print_msg(f"Maintainers:{prefix}{prefix.join(maintainers)}")
Ejemplo n.º 7
0
def add_extra_section(recipe, maintainers: Optional[List] = None):
    maintainers = maintainers or [get_git_current_user()]
    if "extra" in recipe:
        recipe["extra"]["recipe-maintainers"] = maintainers
    else:
        recipe.add_section({"extra": {"recipe-maintainers": maintainers}})
    prefix = f"\n   - {Fore.LIGHTMAGENTA_EX}"
    print_msg(f"\nMaintainers:{prefix}{prefix.join(maintainers)}")
Ejemplo n.º 8
0
    def _get_pypi_metadata(self, name, version: Optional[str] = None) -> dict:
        """Method responsible to communicate with the pypi api endpoints and
        get the whole metadata available for the specified package and version.

        :param name: Package name
        :param version: Package version
        :return: Pypi metadata
        """
        print_msg("Recovering metadata from pypi...")
        if version:
            url_pypi = PyPi.URL_PYPI_METADATA.format(
                pkg_name=f"{name}/{version}")
        else:
            log.info(
                f"Version for {name} not specified.\nGetting the latest one.")
            url_pypi = PyPi.URL_PYPI_METADATA.format(pkg_name=name)

        metadata = requests.get(url=url_pypi, timeout=5)
        if metadata.status_code != 200:
            raise requests.HTTPError(
                f"It was not possible to recover PyPi metadata for {name}.\n"
                f"Error code: {metadata.status_code}")

        metadata = metadata.json()
        if self._download:
            download_file = os.path.join(
                str(mkdtemp(f"grayskull-pypi-metadata-{name}-")), "pypi.json")
            with open(download_file, "w") as f:
                json.dump(metadata, f, indent=4)
            self.files_to_copy.append(download_file)
        info = metadata["info"]
        project_urls = info.get("project_urls") if info.get(
            "project_urls") else {}
        log.info(f"Package: {name}=={info['version']}")
        log.debug(f"Full PyPI metadata:\n{metadata}")
        return {
            "name": name,
            "version": info["version"],
            "requires_dist": info.get("requires_dist", []),
            "requires_python": info.get("requires_python", None),
            "summary": info.get("summary"),
            "project_url": info.get("project_url"),
            "doc_url": info.get("docs_url"),
            "dev_url": project_urls.get("Source"),
            "url": info.get("home_page"),
            "license": info.get("license"),
            "source": {
                "url":
                "https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/"
                f"{PyPi._get_url_filename(metadata)}",
                "sha256": PyPi.get_sha256_from_pypi_metadata(metadata),
            },
            "sdist_url": PyPi._get_sdist_url_from_pypi(metadata),
        }
Ejemplo n.º 9
0
def get_git_current_user() -> str:
    try:
        github_search = get_git_current_user_metadata()
        if github_search["total_count"] >= 1:
            return github_search["items"][0]["login"]
    except Exception as err:
        log.debug(
            f"Exception occurred when trying to recover user information from github."
            f" Exception: {err}")
    print_msg(f"Using default recipe maintainer:"
              f" {Fore.LIGHTMAGENTA_EX}AddYourGitHubIdHere")
    return "AddYourGitHubIdHere"
Ejemplo n.º 10
0
def get_all_licenses_from_spdx() -> List:
    """Get all licenses available on spdx.org

    :return: List with all licenses information on spdx.org
    """
    response = requests.get(url="https://spdx.org/licenses/licenses.json",
                            timeout=5)
    log.debug(f"Response from spdx.org. Status code:{response.status_code},"
              f" response: {response}")
    if response.status_code != 200:
        raise requests.HTTPError(
            f"It was not possible to communicate with spdx api.\n{response.text}"
        )
    print_msg("Recovering license info from spdx.org ...")
    return [
        lic for lic in response.json()["licenses"]
        if not lic.get("isDeprecatedLicenseId", False)
    ]
Ejemplo n.º 11
0
def get_license_type(path_license: str,
                     default: Optional[str] = None) -> Optional[str]:
    """Function tries to match the license with one of the models present in
    grayskull/license/data

    :param path_license: Path to the license file
    :param default: Default value for the license type
    :return: License type
    """
    with open(path_license, "r") as license_file:
        license_content = license_file.read()
    find_apache = re.findall(
        r"apache\.org\/licenses\/LICENSE\-([0-9])\.([0-9])",
        license_content,
        re.IGNORECASE,
    )
    if find_apache:
        lic_type = find_apache[0]
        return f"Apache-{lic_type[0]}.{lic_type[1]}"
    print_msg(
        f"{Fore.LIGHTBLACK_EX}Matching license file with database from Grayskull..."
    )
    all_licenses = get_all_licenses()
    licenses_text = list(map(itemgetter(1), all_licenses))
    best_match = process.extract(license_content,
                                 licenses_text,
                                 scorer=token_sort_ratio)

    if default and best_match[0][1] < 51:
        log.info(
            f"Match too low for recipe {best_match}, using the default {default}"
        )
        return default

    higher_match = best_match[0]
    equal_values = [
        val[0] for val in best_match if val[1] > (higher_match[1] - 3)
    ]
    if len(equal_values) > 1:
        higher_match = process.extractOne(license_content,
                                          equal_values,
                                          scorer=token_set_ratio)
    index_license = licenses_text.index(higher_match[0])
    return all_licenses[index_license][0]
Ejemplo n.º 12
0
    def _get_sdist_metadata(self, sdist_url: str, name: str) -> dict:
        """Method responsible to return the sdist metadata which is basically
        the metadata present in setup.py and setup.cfg

        :param sdist_url: URL to the sdist package
        :param name: name of the package
        :return: sdist metadata
        """
        temp_folder = mkdtemp(prefix=f"grayskull-{name}-")
        pkg_name = sdist_url.split("/")[-1]
        path_pkg = os.path.join(temp_folder, pkg_name)

        PyPi._download_sdist_pkg(sdist_url=sdist_url, dest=path_pkg)
        if self._download:
            self.files_to_copy.append(path_pkg)
        log.debug(f"Unpacking {path_pkg} to {temp_folder}")
        shutil.unpack_archive(path_pkg, temp_folder)
        print_msg("Recovering information from setup.py")
        with PyPi._injection_distutils(temp_folder) as metadata:
            metadata["sdist_path"] = temp_folder
            return metadata
Ejemplo n.º 13
0
def generate_recipes_from_list(list_pkgs, args):
    for pkg_name in list_pkgs:
        logging.debug(f"Starting grayskull for pkg: {pkg_name}")
        from_local_sdist = origin_is_local_sdist(pkg_name)
        if origin_is_github(pkg_name):
            pypi_label = ""
        elif from_local_sdist:
            pypi_label = " (local)"
        else:
            pypi_label = " (pypi)"
        print_msg(f"{Fore.GREEN}\n\n"
                  f"#### Initializing recipe for "
                  f"{Fore.BLUE}{pkg_name}{pypi_label} {Fore.GREEN}####\n")
        is_pkg_file = Path(pkg_name).is_file() and (not from_local_sdist)
        if is_pkg_file:
            args.output = pkg_name
        try:
            recipe, config = create_python_recipe(
                pkg_name,
                is_strict_cf=args.is_strict_conda_forge,
                download=args.download,
                url_pypi_metadata=args.url_pypi_metadata,
                sections_populate=args.sections_populate,
                from_local_sdist=from_local_sdist,
                extras_require_test=args.extras_require_test,
            )
        except requests.exceptions.HTTPError as err:
            print_msg(
                f"{Fore.RED}Package seems to be missing.\nException: {err}\n\n"
            )
            continue

        if args.sections_populate is None or "extra" in args.sections_populate:
            add_extra_section(recipe, args.maintainers)

        generate_recipe(recipe, config, args.output)
        print_msg(f"\n{Fore.GREEN}#### Recipe generated on "
                  f"{os.path.realpath(args.output)} for {pkg_name} ####\n\n")

        if args.is_recursive and config.missing_deps:
            generate_recipes_from_list(config.missing_deps, args)
Ejemplo n.º 14
0
    def _get_metadata(self) -> dict:
        """Method responsible to get the whole metadata available. It will
        merge metadata from multiple sources (pypi, setup.py, setup.cfg)
        """
        name = self.get_var_content(self["package"]["name"].values[0])
        version = ""
        if self["package"]["version"].values:
            version = self.get_var_content(
                self["package"]["version"].values[0])
        pypi_metadata = self._get_pypi_metadata(name, version)
        sdist_metadata = self._get_sdist_metadata(
            sdist_url=pypi_metadata["sdist_url"], name=name)
        metadata = PyPi._merge_pypi_sdist_metadata(pypi_metadata,
                                                   sdist_metadata)
        log.debug(f"Data merged from pypi, setup.cfg and setup.py: {metadata}")
        license_metadata = PyPi._discover_license(metadata)

        license_file = "PLEASE_ADD_LICENSE_FILE"
        license_name = "Other"
        if license_metadata:
            license_name = license_metadata.name
            if license_metadata.path:
                if license_metadata.is_packaged:
                    license_file = license_metadata.path
                else:
                    license_file = os.path.basename(license_metadata.path)
                    self.files_to_copy.append(license_metadata.path)

        print_msg(f"License type: {Fore.LIGHTMAGENTA_EX}{license_name}")
        print_msg(f"License file: {Fore.LIGHTMAGENTA_EX}{license_file}")

        all_requirements = self._extract_requirements(metadata)
        all_requirements["host"] = solve_list_pkg_name(
            all_requirements["host"], self.PYPI_CONFIG)
        all_requirements["run"] = solve_list_pkg_name(all_requirements["run"],
                                                      self.PYPI_CONFIG)
        if self._is_strict_cf:
            all_requirements["host"] = clean_deps_for_conda_forge(
                all_requirements["host"])
            all_requirements["run"] = clean_deps_for_conda_forge(
                all_requirements["run"])
        print_requirements(all_requirements)

        test_entry_points = PyPi._get_test_entry_points(
            metadata.get("entry_points"))
        test_imports = PyPi._get_test_imports(metadata, pypi_metadata["name"])
        return {
            "package": {
                "name": name,
                "version": metadata["version"]
            },
            "build": {
                "entry_points": metadata.get("entry_points")
            },
            "requirements": all_requirements,
            "test": {
                "imports": test_imports,
                "commands": ["pip check"] + test_entry_points,
                "requires": "pip",
            },
            "about": {
                "home":
                metadata["url"]
                if metadata.get("url") else metadata.get("project_url"),
                "summary":
                metadata.get("summary"),
                "doc_url":
                metadata.get("doc_url"),
                "dev_url":
                metadata.get("dev_url"),
                "license":
                license_name,
                "license_file":
                license_file,
            },
            "source": metadata.get("source", {}),
        }
Ejemplo n.º 15
0
    def _injection_distutils(folder: str) -> dict:
        """This is a bit of "dark magic", please don't do it at home.
        It is injecting code in the distutils.core.setup and replacing the
        setup function by the inner function __fake_distutils_setup.
        This method is a contextmanager, after leaving the context it will return
        with the normal implementation of the distutils.core.setup.
        This method is necessary because some information are missing from the
        pypi metadata and also for those packages which the pypi metadata is missing.

        :param folder: Path to the folder where the sdist package was extracted
        :yield: return the metadata from sdist
        """
        from distutils import core

        setup_core_original = core.setup
        old_dir = os.getcwd()
        path_setup = search_setup_root(folder)
        os.chdir(os.path.dirname(str(path_setup)))

        data_dist = {}

        def __fake_distutils_setup(*args, **kwargs):
            if not isinstance(kwargs, dict) or not kwargs:
                return

            def _fix_list_requirements(key_deps: str) -> List:
                """Fix when dependencies have lists inside of another sequence"""
                val_deps = kwargs.get(key_deps)
                if not val_deps:
                    return val_deps
                list_req = []
                if isinstance(val_deps, str):
                    val_deps = [val_deps]
                for val in val_deps:
                    if isinstance(val, (tuple, list)):
                        list_req.extend(list(map(str, val)))
                    else:
                        list_req.append(str(val))
                return list_req

            if "setup_requires" in kwargs:
                kwargs["setup_requires"] = _fix_list_requirements(
                    "setup_requires")
            if "install_requires" in kwargs:
                kwargs["install_requires"] = _fix_list_requirements(
                    "install_requires")

            data_dist.update(kwargs)
            if not data_dist.get("setup_requires"):
                data_dist["setup_requires"] = []
            data_dist["setup_requires"] += (kwargs.get("setup_requires")
                                            if kwargs.get("setup_requires")
                                            else [])

            if "use_scm_version" in data_dist and kwargs["use_scm_version"]:
                log.debug("setuptools_scm found on setup.py")
                if "setuptools_scm" not in data_dist["setup_requires"]:
                    data_dist["setup_requires"] += ["setuptools_scm"]
                if "setuptools-scm" in data_dist["setup_requires"]:
                    data_dist["setup_requires"].remove("setuptools-scm")

            if kwargs.get("ext_modules", None):
                data_dist["compilers"] = ["c"]
                if len(kwargs["ext_modules"]) > 0:
                    for ext_mod in kwargs["ext_modules"]:
                        if (hasattr(ext_mod, "has_f2py_sources")
                                and ext_mod.has_f2py_sources()):
                            data_dist["compilers"].append("fortran")
                            break
            log.debug(f"Injection distutils all arguments: {kwargs}")
            if data_dist.get("run_py", False):
                del data_dist["run_py"]
                return
            setup_core_original(*args, **kwargs)

        try:
            core.setup = __fake_distutils_setup
            path_setup = str(path_setup)
            print_msg("Executing injected distutils...")
            PyPi.__run_setup_py(path_setup, data_dist)
            if not data_dist or not data_dist.get("install_requires", None):
                print_msg("No data was recovered from setup.py."
                          " Forcing to execute the setup.py as script")
                PyPi.__run_setup_py(path_setup, data_dist, run_py=True)
            yield data_dist
        except BaseException as err:
            log.debug(
                f"Exception occurred when executing sdist injection: {err}")
            yield data_dist
        finally:
            core.setup = setup_core_original
            os.chdir(old_dir)
Ejemplo n.º 16
0
def get_metadata(recipe, config) -> dict:
    """Method responsible to get the whole metadata available. It will
    merge metadata from multiple sources (pypi, setup.py, setup.cfg)
    """
    name = config.name
    sdist_metadata, pypi_metadata = get_origin_wise_metadata(config)
    metadata = merge_pypi_sdist_metadata(pypi_metadata, sdist_metadata, config)
    log.debug(f"Data merged from pypi, setup.cfg and setup.py: {metadata}")
    if metadata.get("scripts") is not None:
        config.is_arch = True
        print_msg(
            f"{Fore.YELLOW}scripts detected. Package not eligible for noarch.")

    license_metadata = discover_license(metadata)

    license_file = "PLEASE_ADD_LICENSE_FILE"
    license_name = "Other"
    if license_metadata:
        license_name = license_metadata.name
        if license_metadata.path:
            if license_metadata.is_packaged:
                license_file = license_metadata.path
            else:
                license_file = os.path.basename(license_metadata.path)
                config.files_to_copy.append(license_metadata.path)

    print_msg(f"License type: {Fore.LIGHTMAGENTA_EX}{license_name}")
    print_msg(f"License file: {Fore.LIGHTMAGENTA_EX}{license_file}")

    all_requirements = extract_requirements(metadata, config, recipe)

    if all_requirements.get("host"):
        all_requirements["host"] = solve_list_pkg_name(
            all_requirements["host"], PYPI_CONFIG)
        all_requirements["host"] = ensure_pep440_in_req_list(
            all_requirements["host"])
    if all_requirements.get("run"):
        all_requirements["run"] = solve_list_pkg_name(all_requirements["run"],
                                                      PYPI_CONFIG)
        all_requirements["run"] = ensure_pep440_in_req_list(
            all_requirements["run"])
    if config.is_strict_cf:
        all_requirements["host"] = clean_deps_for_conda_forge(
            all_requirements["host"], config.py_cf_supported[0])
        all_requirements["run"] = clean_deps_for_conda_forge(
            all_requirements["run"], config.py_cf_supported[0])

    all_missing_deps = print_requirements(all_requirements)
    config.missing_deps = all_missing_deps
    test_imports = get_test_imports(metadata, metadata["name"])
    test_commands = ["pip check"]
    test_requirements = ["pip"]
    test_requirements.extend(
        get_test_requirements(metadata, config.extras_require_test))
    if any("pytest" in req for req in test_requirements):
        for module in test_imports:
            test_commands.append("pytest --pyargs " + module)
    test_commands.extend(
        get_test_entry_points(metadata.get("entry_points", [])))
    return {
        "package": {
            "name": name,
            "version": metadata["version"]
        },
        "build": {
            "entry_points": metadata.get("entry_points")
        },
        "requirements": all_requirements,
        "test": {
            "imports": test_imports,
            "commands": test_commands,
            "requires": test_requirements,
        },
        "about": {
            "home":
            metadata["url"]
            if metadata.get("url") else metadata.get("project_url"),
            "summary":
            metadata.get("summary"),
            "doc_url":
            metadata.get("doc_url"),
            "dev_url":
            metadata.get("dev_url"),
            "license":
            license_name,
            "license_file":
            license_file,
        },
        "source": metadata.get("source", {}),
    }
Ejemplo n.º 17
0
 def print_req(name, list_req: List):
     prefix_req = f"{Fore.LIGHTBLACK_EX}\n   - {Fore.LIGHTCYAN_EX}"
     print_msg(f"{Fore.LIGHTBLACK_EX}{name} requirements:"
               f"{prefix_req}{prefix_req.join(list_req)}")
Ejemplo n.º 18
0
def main(args=None):
    if not args:
        args = sys.argv[1:] or ["--help"]

    parser = argparse.ArgumentParser(
        description="Grayskull - Conda recipe generator")
    pypi_parser = parser.add_subparsers(
        help="Options to generate PyPI recipes")
    pypi_cmds = pypi_parser.add_parser("pypi",
                                       help="Generate recipes based on PyPI")
    pypi_cmds.add_argument("pypi_packages",
                           nargs="+",
                           help="Specify the PyPI packages name.",
                           default=[])
    pypi_cmds.add_argument(
        "--download",
        "-d",
        dest="download",
        action="store_true",
        default=False,
        help="Download the sdist package and PyPI information in the same folder"
        " the recipe is located.",
    )
    pypi_cmds.add_argument(
        "--maintainers",
        "-m",
        dest="maintainers",
        nargs="+",
        help="List of maintainers which will be added to the recipe.",
    )
    parser.add_argument(
        "--version",
        "-v",
        default=False,
        action="store_true",
        dest="version",
        help="Print Grayskull version and exit",
    )
    parser.add_argument(
        "--heman",
        "--shera",
        default=False,
        action="store_true",
        dest="grayskull_power",
        help=argparse.SUPPRESS,
    )
    pypi_cmds.add_argument(
        "--output",
        "-o",
        dest="output",
        default=".",
        help="Path to where the recipe will be created",
    )
    pypi_cmds.add_argument(
        "--stdout",
        dest="stdout",
        default=True,
        help="Disable or enable stdout, if it is False, Grayskull"
        " will disable the prints. Default is True",
    )
    pypi_cmds.add_argument(
        "--list-missing-deps",
        default=False,
        action="store_true",
        dest="list_missing_deps",
        help=
        "After the execution Grayskull will print all the missing dependencies.",
    )
    pypi_cmds.add_argument(
        "--strict-conda-forge",
        default=False,
        action="store_true",
        dest="is_strict_conda_forge",
        help="It will generate the recipes strict for the conda-forge channel.",
    )
    pypi_cmds.add_argument(
        "--pypi-url",
        default="https://pypi.org/pypi/",
        dest="url_pypi_metadata",
        help="Pypi url server",
    )
    pypi_cmds.add_argument(
        "--recursive",
        "-r",
        default=False,
        action="store_true",
        dest="is_recursive",
        help="Recursively run grayskull on missing dependencies.",
    )
    pypi_cmds.add_argument(
        "--sections",
        "-s",
        default=None,
        required=False,
        choices=(
            "package",
            "source",
            "build",
            "requirements",
            "test",
            "about",
            "extra",
        ),
        nargs="+",
        dest="sections_populate",
        help=
        "If sections are specified, grayskull will populate just the sections "
        "informed.",
    )
    pypi_cmds.add_argument(
        "--extras-require-test",
        default=None,
        dest="extras_require_test",
        help="Extra requirements to run tests.",
    )

    args = parser.parse_args(args)

    if args.version:
        print(grayskull.__version__)
        return

    logging.debug(f"All arguments received: args: {args}")

    if args.grayskull_power:
        print(f"{Fore.BLUE}By the power of Grayskull...\n"
              f"{Style.BRIGHT}I have the power!")
        return

    CLIConfig().stdout = args.stdout
    CLIConfig().list_missing_deps = args.list_missing_deps

    print_msg(Style.RESET_ALL)
    print_msg(clear_screen())

    generate_recipes_from_list(args.pypi_packages, args)
Ejemplo n.º 19
0
def test_print_stdout(capsys):
    CLIConfig().stdout = True
    print_msg("TEST-OUTPUT")
    captured_out = capsys.readouterr()
    assert captured_out.out == "TEST-OUTPUT\n"
Ejemplo n.º 20
0
def main(args=None):
    if not args:
        args = sys.argv[1:] if sys.argv[1:] else ["--help"]

    parser = argparse.ArgumentParser(
        description="Grayskull - Conda recipe generator")
    pypi_parser = parser.add_subparsers(
        help="Options to generate PyPI recipes")
    pypi_cmds = pypi_parser.add_parser("pypi",
                                       help="Generate recipes based on PyPI")
    pypi_cmds.add_argument("pypi_packages",
                           nargs="+",
                           help="Specify the PyPI packages name.",
                           default=[])
    pypi_cmds.add_argument(
        "--download",
        "-d",
        dest="download",
        action="store_true",
        default=False,
        help="Download the sdist package and PyPI information in the same folder"
        " the recipe is located.",
    )
    pypi_cmds.add_argument(
        "--maintainers",
        "-m",
        dest="maintainers",
        nargs="+",
        help="List of maintainers which will be added to the recipe.",
    )
    parser.add_argument(
        "--version",
        "-v",
        default=False,
        action="store_true",
        dest="version",
        help="Print Grayskull version and exit",
    )
    parser.add_argument(
        "--heman",
        "--shera",
        default=False,
        action="store_true",
        dest="grayskull_power",
        help=argparse.SUPPRESS,
    )
    pypi_cmds.add_argument(
        "--output",
        "-o",
        dest="output",
        default=".",
        help="Path to where the recipe will be created",
    )
    pypi_cmds.add_argument(
        "--stdout",
        dest="stdout",
        default=True,
        help="Disable or enable stdout, if it is False, Grayskull"
        " will disable the prints. Default is True",
    )
    pypi_cmds.add_argument(
        "--list-missing-deps",
        default=False,
        action="store_true",
        dest="list_missing_deps",
        help=
        "After the execution Grayskull will print all the missing dependencies.",
    )
    pypi_cmds.add_argument(
        "--strict-conda-forge",
        default=False,
        action="store_true",
        dest="is_strict_conda_forge",
        help="It will generate the recipes strict for the conda-forge channel.",
    )

    args = parser.parse_args(args)

    if args.version:
        print(grayskull.__version__)
        return

    logging.debug(f"All arguments received: args: {args}")

    if args.grayskull_power:
        print(f"{Fore.BLUE}By the power of Grayskull...\n"
              f"{Style.BRIGHT}I have the power!")
        return

    CLIConfig().stdout = args.stdout
    CLIConfig().list_missing_deps = args.list_missing_deps

    print_msg(Style.RESET_ALL)
    print_msg(clear_screen())

    for pkg_name in args.pypi_packages:
        logging.debug(f"Starting grayskull for pkg: {pkg_name}")
        pypi_label = "" if origin_is_github(pkg_name) else " (pypi)"
        print_msg(f"{Fore.GREEN}\n\n"
                  f"#### Initializing recipe for "
                  f"{Fore.BLUE}{pkg_name}{pypi_label} {Fore.GREEN}####\n")
        pkg_name, pkg_version = parse_pkg_name_version(pkg_name)
        try:
            recipe = GrayskullFactory.create_recipe(
                "pypi",
                pkg_name,
                pkg_version,
                download=args.download,
                is_strict_cf=args.is_strict_conda_forge,
            )
        except requests.exceptions.HTTPError as err:
            print_msg(
                f"{Fore.RED}Package seems to be missing on pypi.\nException: {err}\n\n"
            )
            continue
        recipe.generate_recipe(args.output, mantainers=args.maintainers)
        print_msg(f"\n{Fore.GREEN}#### Recipe generated on "
                  f"{os.path.realpath(args.output)} for {pkg_name} ####\n")
Ejemplo n.º 21
0
def main(args=None):
    if args is None:
        args = sys.argv[1:]

    parser = argparse.ArgumentParser(
        description="Grayskull - Conda recipe generator")
    pypi_parser = parser.add_subparsers(
        help="Options to generate PyPI recipes")
    pypi_cmds = pypi_parser.add_parser("pypi",
                                       help="Generate recipes based on PyPI")
    pypi_cmds.add_argument("pypi_packages",
                           nargs="+",
                           help="Specify the PyPI packages name.",
                           default=[])
    pypi_cmds.add_argument(
        "--download",
        "-d",
        dest="download",
        action="store_true",
        default=False,
        help="Download the sdist package and PyPI information in the same folder"
        " the recipe is located.",
    )
    pypi_cmds.add_argument(
        "--maintainers",
        "-m",
        dest="maintainers",
        nargs="+",
        help="List of maintainers which will be added to the recipe.",
    )
    parser.add_argument(
        "--version",
        "-v",
        default=False,
        action="store_true",
        dest="version",
        help="Print Grayskull version and exit",
    )
    parser.add_argument(
        "--heman",
        "--shera",
        default=False,
        action="store_true",
        dest="grayskull_power",
        help=argparse.SUPPRESS,
    )
    pypi_cmds.add_argument(
        "--output",
        "-o",
        dest="output",
        default=".",
        help="Path to where the recipe will be created",
    )
    pypi_cmds.add_argument(
        "--stdout",
        dest="stdout",
        default=True,
        help="Disable or enable stdout, if it is False, Grayskull"
        " will disable the prints. Default is True",
    )

    args = parser.parse_args(args)

    if args.version:
        print(grayskull.__version__)
        return

    logging.debug(f"All arguments received: args: {args}")

    if args.grayskull_power:
        print(f"{Fore.BLUE}By the power of Grayskull...\n"
              f"{Style.BRIGHT}I have the power!")
        return

    CLIConfig().stdout = args.stdout

    print_msg(Style.RESET_ALL)
    print_msg(clear_screen())

    for pkg_name in args.pypi_packages:
        logging.debug(f"Starting grayskull for pkg: {pkg_name}")
        print_msg(f"{Fore.GREEN}\n\n"
                  f"#### Initializing recipe for "
                  f"{Fore.BLUE}{pkg_name} (pypi) {Fore.GREEN}####\n")
        pkg_name, pkg_version = parse_pkg_name_version(pkg_name)
        recipe = GrayskullFactory.create_recipe("pypi",
                                                pkg_name,
                                                pkg_version,
                                                download=args.download)
        recipe.generate_recipe(args.output, mantainers=args.maintainers)
        print_msg(f"\n{Fore.GREEN}#### Recipe generated on "
                  f"{os.path.realpath(args.output)} for {pkg_name} ####\n")
Ejemplo n.º 22
0
def test_disabled_print(capsys):
    CLIConfig().stdout = False
    print_msg("TEST-OUTPUT")
    captured_out = capsys.readouterr()
    assert captured_out.out == ""