Пример #1
0
def test_parse_pep420_namespace_package() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-pep420-package/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["package_dir"] == {}
    assert paths["packages"] == ["foo.my_package"]
    assert paths["py_modules"] == []
Пример #2
0
def test_parse_src_package_by_include():
    metadata = Metadata(FIXTURES /
                        "projects/demo-src-package-include/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["package_dir"] == {}
    assert paths["packages"] == ["sub.my_package"]
    assert paths["py_modules"] == []
Пример #3
0
def test_explicit_package_dir() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-explicit-package-dir/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["packages"] == ["my_package"]
    assert paths["py_modules"] == []
    assert paths["package_dir"] == {"": "foo"}
Пример #4
0
def test_package_with_old_include() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-package-include-old/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["py_modules"] == []
    assert paths["packages"] == ["my_package"]
    assert paths["package_dir"] == {}
    assert paths["package_data"] == {"": ["*"]}
    assert not metadata.classifiers
Пример #5
0
def test_parse_module() -> None:
    metadata = Metadata(FIXTURES / "projects/demo-module/pyproject.toml")
    assert metadata.name == "demo-module"
    assert metadata.version == "0.1.0"
    assert metadata.author == ""
    assert metadata.author_email == "frostming <*****@*****.**>"
    paths = metadata.convert_package_paths()
    assert sorted(paths["py_modules"]) == ["bar_module", "foo_module"]
    assert paths["packages"] == []
    assert paths["package_dir"] == {}
Пример #6
0
def test_autogen_classifiers():
    metadata = Metadata(FIXTURES / "projects/demo-module/pyproject.toml")
    classifiers = metadata.classifiers
    for python_version in ("3", "3.5", "3.6", "3.7", "3.8", "3.9"):
        assert f"Programming Language :: Python :: {python_version}" in classifiers
    assert "Programming Language :: Python :: 2.7" not in classifiers
    assert "License :: OSI Approved :: MIT License" in classifiers
Пример #7
0
def test_project_name_and_version_missing() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-no-name-nor-version/pyproject.toml")
    assert metadata.version is None
    assert metadata.name is None
    assert metadata.project_name is None
    assert metadata.project_filename == "UNKNOWN"
Пример #8
0
def test_project_version_use_scm(project_with_scm) -> None:
    metadata = Metadata(project_with_scm / "pyproject.toml")
    assert metadata.version == "0.1.0"
    project_with_scm.joinpath("test.txt").write_text("hello\n")
    subprocess.check_call(["git", "add", "test.txt"])
    date = datetime.utcnow().strftime("%Y%m%d")
    assert metadata.version == f"0.1.0+d{date}"
    subprocess.check_call(["git", "commit", "-m", "add test.txt"])
    assert "0.1.1.dev1+g" in metadata.version
Пример #9
0
def test_parse_package_with_extras() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-combined-extras/pyproject.toml")
    assert metadata.dependencies == ["urllib3"]
    assert metadata.optional_dependencies == {
        "be": ["idna"],
        "te": ["chardet"],
        "all": ["idna", "chardet"],
    }
    assert metadata.requires_extra == {
        "be": ['idna; extra == "be"'],
        "te": ['chardet; extra == "te"'],
        "all": ['idna; extra == "all"', 'chardet; extra == "all"'],
    }
Пример #10
0
    def read_pyproject_toml(self, filepath):
        from pdm.pep517.metadata import Metadata

        try:
            metadata = Metadata(filepath)
        except ValueError:
            return {}
        return {
            "name": metadata.name,
            "version": metadata.version,
            "install_requires": metadata.dependencies,
            "extras_require": metadata.optional_dependencies,
            "python_requires": metadata.requires_python,
        }
Пример #11
0
def get_requires_for_build_wheel(
        config_settings: Optional[Mapping[str, Any]] = None) -> List[str]:
    """
    Returns an additional list of requirements for building, as PEP508 strings,
    above and beyond those specified in the pyproject.toml file.

    When C-extension build is needed, setuptools should be required, otherwise
    just return an empty list.
    """
    meta = Metadata(Path("pyproject.toml"))
    if meta.build:
        return ["setuptools>=40.8.0"]
    else:
        return []
Пример #12
0
 def meta(self) -> Metadata:
     if not self._meta:
         self._meta = Metadata(self.location / "pyproject.toml")
     return self._meta
Пример #13
0
class Builder:
    """Base class for building and distributing a package from given path."""

    DEFAULT_EXCLUDES = ["ez_setup", "*__pycache__", "tests", "tests.*"]

    def __init__(self, location: Union[str, Path]) -> None:
        self._old_cwd = None
        self.location = Path(location).absolute()
        self._meta = None

    @property
    def meta(self) -> Metadata:
        if not self._meta:
            self._meta = Metadata(self.location / "pyproject.toml")
            # Open the validation for next release
            self._meta.validate(False)
        return self._meta

    def __enter__(self) -> "Builder":
        self._old_cwd = os.getcwd()
        os.chdir(self.location)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        os.chdir(self._old_cwd)

    def build(self, build_dir: str, **kwargs) -> str:
        raise NotImplementedError

    def _find_files_iter(self, for_sdist: bool = False) -> Iterator[str]:
        includes = set()
        find_froms = set()
        excludes = set()
        dont_find_froms = set()
        source_includes = self.meta.source_includes or ["tests"]
        meta_includes = itertools.chain(self.meta.includes, source_includes)
        meta_excludes = list(self.meta.excludes)

        if not for_sdist:
            # exclude source-includes for non-sdist builds
            meta_excludes.extend(source_includes)

        for pat in meta_includes:
            if os.path.basename(pat) == "*":
                pat = pat[:-2]
            if "*" in pat or os.path.isfile(pat):
                includes.add(pat)
            else:
                find_froms.add(pat)
        if not self.meta.includes:
            top_packages = _find_top_packages(self.meta.package_dir or ".")
            if top_packages:
                find_froms.update(top_packages)
            else:
                includes.add(f"{self.meta.package_dir or '.'}/*.py")

        for pat in meta_excludes:
            if "*" in pat or os.path.isfile(pat):
                excludes.add(pat)
            else:
                dont_find_froms.add(pat)

        include_globs = {
            path: key
            for key in includes for path in glob.glob(key)
        }
        excludes_globs = {
            path: key
            for key in excludes for path in glob.glob(key)
        }

        includes, excludes = _merge_globs(include_globs, excludes_globs)
        for path in find_froms:
            if any(_match_path(path, item) for item in dont_find_froms):
                continue
            path_base = os.path.dirname(path)
            if not path_base or path_base == ".":
                # the path is top level itself
                path_base = path

            for root, dirs, filenames in os.walk(path):

                for filename in filenames:
                    if filename.endswith(".pyc") or any(
                            _match_path(os.path.join(root, filename), item)
                            for item in excludes):
                        continue
                    yield os.path.join(root, filename)

        for path in includes:
            if os.path.isfile(path):
                yield path
        if not for_sdist:
            return

        if self.meta.build and os.path.isfile(self.meta.build):
            yield self.meta.build

        for pat in ("COPYING", "LICENSE"):
            for path in glob.glob(pat + "*"):
                if os.path.isfile(path):
                    yield path

        if self.meta.readme and os.path.isfile(self.meta.readme):
            yield self.meta.readme

        if self.meta.filepath.exists():
            yield "pyproject.toml"

    def find_files_to_add(self, for_sdist: bool = False) -> List[Path]:
        """Traverse the project path and return a list of file names
        that should be included in a sdist distribution.
        If for_sdist is True, will include files like LICENSE, README and pyproject
        Produce a paths list relative to the source dir.
        """
        return sorted(set(Path(p) for p in self._find_files_iter(for_sdist)))

    def format_setup_py(self) -> str:
        before, extra, after = [], [], []
        meta = self.meta
        kwargs = {
            "name": meta.name,
            "version": meta.version,
            "author": meta.author,
            "license": meta.license_type,
            "author_email": meta.author_email,
            "maintainer": meta.maintainer,
            "maintainer_email": meta.maintainer_email,
            "description": meta.description,
            "url": (meta.project_urls or {}).get("homepage", ""),
        }

        if meta.build:
            # The build script must contain a `build(setup_kwargs)`, we just import
            # and execute it.
            after.extend([
                "from {} import build\n".format(meta.build.split(".")[0]),
                "build(setup_kwargs)\n",
            ])

        package_paths = meta.convert_package_paths()
        if package_paths["packages"]:
            extra.append("    'packages': {},\n".format(
                _format_list(package_paths["packages"], 8)))
        if package_paths["package_dir"]:
            extra.append("    'package_dir': {!r},\n".format(
                package_paths["package_dir"]))
        if package_paths["package_data"]:
            extra.append("    'package_data': {!r},\n".format(
                package_paths["package_data"]))
        if package_paths["exclude_package_data"]:
            extra.append("    'exclude_package_data': {!r},\n".format(
                package_paths["exclude_package_data"]))
        if meta.readme:
            before.append(OPEN_README.format(readme=meta.readme))
        elif meta.long_description:
            before.append("long_description = '''{}'''\n".format(
                repr(meta.long_description)[1:-1]))
        else:
            before.append("long_description = None\n")
        if meta.long_description_content_type:
            extra.append("    'long_description_content_type': {!r},\n".format(
                meta.long_description_content_type))

        if meta.keywords:
            extra.append("    'keywords': {!r},\n".format(meta.keywords))
        if meta.classifiers:
            extra.append("    'classifiers': {},\n".format(
                _format_list(meta.classifiers, 8)))
        if meta.dependencies:
            before.append("INSTALL_REQUIRES = {}\n".format(
                _format_list(meta.dependencies)))
            extra.append("    'install_requires': INSTALL_REQUIRES,\n")
        if meta.optional_dependencies:
            before.append("EXTRAS_REQUIRE = {}\n".format(
                _format_dict_list(meta.optional_dependencies)))
            extra.append("    'extras_require': EXTRAS_REQUIRE,\n")
        if meta.requires_python:
            extra.append("    'python_requires': {!r},\n".format(
                meta.requires_python))
        if meta.entry_points:
            before.append("ENTRY_POINTS = {}\n".format(
                _format_dict_list(meta.entry_points)))
            extra.append("    'entry_points': ENTRY_POINTS,\n")
        return SETUP_FORMAT.format(before="".join(before),
                                   after="".join(after),
                                   extra="".join(extra),
                                   **kwargs)

    def format_pkginfo(self, full=True) -> str:
        meta = self.meta
        content = METADATA_BASE.format(
            name=meta.name or "UNKNOWN",
            version=meta.version or "UNKNOWN",
            license=meta.license_type or "UNKNOWN",
            description=meta.description or "UNKNOWN",
        )

        # Optional fields
        if meta.keywords:
            content += "Keywords: {}\n".format(",".join(meta.keywords))

        if meta.author:
            content += "Author: {}\n".format(meta.author)

        if meta.author_email:
            content += "Author-email: {}\n".format(meta.author_email)

        if meta.maintainer:
            content += "Maintainer: {}\n".format(meta.maintainer)

        if meta.maintainer_email:
            content += "Maintainer-email: {}\n".format(meta.maintainer_email)

        if meta.requires_python:
            content += "Requires-Python: {}\n".format(meta.requires_python)

        for classifier in meta.classifiers or []:
            content += "Classifier: {}\n".format(classifier)

        if full:
            for dep in sorted(meta.dependencies):
                content += "Requires-Dist: {}\n".format(dep)

        for extra, reqs in sorted(meta.requires_extra.items()):
            content += "Provides-Extra: {}\n".format(extra)
            if full:
                for dep in reqs:
                    content += "Requires-Dist: {}\n".format(dep)

        for url in sorted(meta.project_urls or {}):
            content += "Project-URL: {}, {}\n".format(url,
                                                      meta.project_urls[url])

        if meta.long_description_content_type:
            content += "Description-Content-Type: {}\n".format(
                meta.long_description_content_type)
        if meta.long_description:
            readme = meta.long_description
            if full:
                content += "\n" + readme + "\n"
            else:
                content += "Description: {}\n".format(
                    textwrap.indent(readme, " " * 8,
                                    lambda line: True).lstrip())

        return content

    def ensure_setup_py(self, clean: bool = True) -> Path:
        """Ensures the requirement has a setup.py ready."""
        # XXX: Currently only handle PDM project, and do nothing if not.

        setup_py_path = self.location.joinpath("setup.py")
        if setup_py_path.is_file():
            return setup_py_path

        setup_py_path.write_text(self.format_setup_py(), encoding="utf-8")

        # Clean this temp file when process exits
        def cleanup():
            try:
                setup_py_path.unlink()
            except OSError:
                pass

        if clean:
            atexit.register(cleanup)
        return setup_py_path
Пример #14
0
def test_parse_src_package() -> None:
    metadata = Metadata(FIXTURES / "projects/demo-src-package/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["packages"] == ["my_package"]
    assert paths["py_modules"] == []
    assert paths["package_dir"] == {"": "src"}
Пример #15
0
def test_parse_error_package() -> None:
    metadata = Metadata(FIXTURES /
                        "projects/demo-package-include-error/pyproject.toml")
    with pytest.raises(ValueError):
        metadata.convert_package_paths()
Пример #16
0
def _get_current_version():
    from pdm.pep517.metadata import Metadata

    metadata = Metadata(PROJECT_DIR / "pyproject.toml")
    return metadata.version
Пример #17
0
def test_src_dir_containing_modules() -> None:
    metadata = Metadata(FIXTURES / "projects/demo-src-pymodule/pyproject.toml")
    paths = metadata.convert_package_paths()
    assert paths["package_dir"] == {"": "src"}
    assert not paths["packages"]
    assert paths["py_modules"] == ["foo_module"]
Пример #18
0
 def meta(self) -> Metadata:
     if not self._meta:
         self._meta = Metadata(self.location / "pyproject.toml")
         # Open the validation for next release
         self._meta.validate(False)
     return self._meta
Пример #19
0
class Builder:
    """Base class for building and distributing a package from given path."""

    DEFAULT_EXCLUDES = ["build"]

    def __init__(
        self,
        location: Union[str, Path],
        config_settings: Optional[Mapping[str, Any]] = None,
    ) -> None:
        self._old_cwd: Optional[str] = None
        self.location = Path(location).absolute()
        self.config_settings = config_settings
        self._meta: Optional[Metadata] = None

    @property
    def meta(self) -> Metadata:
        if not self._meta:
            self._meta = Metadata(self.location / "pyproject.toml")
            # Open the validation for next release
            self._meta.validate(False)
        return self._meta

    @property
    def meta_version(self) -> str:
        meta_version = self.meta.version
        if meta_version is None:
            return "0.0.0"
        return to_filename(safe_version(meta_version))

    def __enter__(self: T) -> T:
        self._old_cwd = os.getcwd()
        os.chdir(self.location)
        return self

    def __exit__(self, *args: Any) -> None:
        assert self._old_cwd
        os.chdir(self._old_cwd)

    def build(self, build_dir: str, **kwargs: Any) -> str:
        raise NotImplementedError

    def _get_include_and_exclude_paths(self,
                                       for_sdist: bool = False
                                       ) -> Tuple[List[str], List[str]]:
        includes = set()
        excludes = set(self.DEFAULT_EXCLUDES)

        meta_excludes = list(self.meta.excludes)
        source_includes = self.meta.source_includes or ["tests"]
        if not for_sdist:
            # exclude source-includes for non-sdist builds
            meta_excludes.extend(source_includes)

        if not self.meta.includes:
            top_packages = _find_top_packages(self.meta.package_dir or ".")
            if top_packages:
                includes.update(top_packages)
            else:
                includes.add(f"{self.meta.package_dir or '.'}/*.py")
        else:
            includes.update(self.meta.includes)

        includes.update(source_includes)
        excludes.update(meta_excludes)

        include_globs = {
            os.path.normpath(path): key
            for key in includes for path in glob.iglob(key, recursive=True)
        }

        excludes_globs = {
            os.path.normpath(path): key
            for key in excludes for path in glob.iglob(key, recursive=True)
        }

        include_paths, exclude_paths = _merge_globs(include_globs,
                                                    excludes_globs)
        return sorted(include_paths), sorted(exclude_paths)

    def _is_excluded(self, path: str, exclude_paths: List[str]) -> bool:
        return any(
            is_same_or_descendant_path(path, exclude_path)
            for exclude_path in exclude_paths)

    def _find_files_iter(self, for_sdist: bool = False) -> Iterator[str]:
        includes, excludes = self._get_include_and_exclude_paths(for_sdist)
        for include_path in includes:
            path = Path(include_path)
            if path.is_file():
                yield include_path
                continue
            # The path is a directory name
            for path in path.glob("**/*"):
                if not path.is_file():
                    continue

                rel_path = path.absolute().relative_to(
                    self.location).as_posix()
                if path.name.endswith(".pyc") or self._is_excluded(
                        rel_path, excludes):
                    continue

                yield rel_path

        if not for_sdist:
            return

        if self.meta.build and os.path.isfile(self.meta.build):
            yield self.meta.build

        for pat in ("COPYING", "LICENSE"):
            for p in glob.glob(pat + "*"):
                if os.path.isfile(p):
                    yield p

        if self.meta.readme and os.path.isfile(self.meta.readme):
            yield self.meta.readme

        if self.meta.filepath.exists():
            yield self.meta.filepath.name

    def find_files_to_add(self, for_sdist: bool = False) -> List[Path]:
        """Traverse the project path and return a list of file names
        that should be included in a sdist distribution.
        If for_sdist is True, will include files like LICENSE, README and pyproject
        Produce a paths list relative to the source dir.
        """
        return sorted({Path(p) for p in self._find_files_iter(for_sdist)})

    def format_setup_py(self) -> str:
        before, extra, after = [], [], []
        meta = self.meta
        kwargs = {
            "name": meta.name,
            "version": meta.version,
            "author": meta.author,
            "license": meta.license_type,
            "author_email": meta.author_email,
            "maintainer": meta.maintainer,
            "maintainer_email": meta.maintainer_email,
            "description": meta.description,
            "url": (meta.project_urls or {}).get("homepage", ""),
        }

        if meta.build:
            # The build script must contain a `build(setup_kwargs)`, we just import
            # and execute it.
            after.extend([
                "from {} import build\n".format(meta.build.split(".")[0]),
                "build(setup_kwargs)\n",
            ])

        package_paths = meta.convert_package_paths()
        if package_paths["packages"]:
            extra.append("    'packages': {},\n".format(
                _format_list(package_paths["packages"], 8)))
        if package_paths["package_dir"]:
            extra.append("    'package_dir': {!r},\n".format(
                package_paths["package_dir"]))
        if package_paths["package_data"]:
            extra.append("    'package_data': {!r},\n".format(
                package_paths["package_data"]))
        if package_paths["exclude_package_data"]:
            extra.append("    'exclude_package_data': {!r},\n".format(
                package_paths["exclude_package_data"]))
        if meta.readme:
            before.append(OPEN_README.format(readme=meta.readme))
        elif meta.long_description:
            before.append("long_description = '''{}'''\n".format(
                repr(meta.long_description)[1:-1]))
        else:
            before.append("long_description = None\n")
        if meta.long_description_content_type:
            extra.append("    'long_description_content_type': {!r},\n".format(
                meta.long_description_content_type))

        if meta.keywords:
            extra.append(f"    'keywords': {meta.keywords!r},\n")
        if meta.classifiers:
            extra.append(
                f"    'classifiers': {_format_list(meta.classifiers, 8)},\n")
        if meta.dependencies:
            before.append(
                f"INSTALL_REQUIRES = {_format_list(meta.dependencies)}\n")
            extra.append("    'install_requires': INSTALL_REQUIRES,\n")
        if meta.optional_dependencies:
            before.append("EXTRAS_REQUIRE = {}\n".format(
                _format_dict_list(meta.optional_dependencies)))
            extra.append("    'extras_require': EXTRAS_REQUIRE,\n")
        if meta.requires_python:
            extra.append(f"    'python_requires': {meta.requires_python!r},\n")
        if meta.entry_points:
            before.append(
                f"ENTRY_POINTS = {_format_dict_list(meta.entry_points)}\n")
            extra.append("    'entry_points': ENTRY_POINTS,\n")
        return SETUP_FORMAT.format(before="".join(before),
                                   after="".join(after),
                                   extra="".join(extra),
                                   **kwargs)

    def format_pkginfo(self, full: bool = True) -> str:
        meta = self.meta
        content = METADATA_BASE.format(
            name=meta.name or "UNKNOWN",
            version=meta.version or "UNKNOWN",
            license=meta.license_type or "UNKNOWN",
            description=meta.description or "UNKNOWN",
        )

        # Optional fields
        if meta.keywords:
            content += "Keywords: {}\n".format(",".join(meta.keywords))

        if meta.author:
            content += f"Author: {meta.author}\n"

        if meta.author_email:
            content += f"Author-email: {meta.author_email}\n"

        if meta.maintainer:
            content += f"Maintainer: {meta.maintainer}\n"

        if meta.maintainer_email:
            content += f"Maintainer-email: {meta.maintainer_email}\n"

        if meta.requires_python:
            content += f"Requires-Python: {meta.requires_python}\n"

        for classifier in meta.classifiers or []:
            content += f"Classifier: {classifier}\n"

        if full:
            for dep in sorted(meta.dependencies):
                content += f"Requires-Dist: {dep}\n"

        for extra, reqs in sorted(meta.requires_extra.items()):
            content += f"Provides-Extra: {extra}\n"
            if full:
                for dep in reqs:
                    content += f"Requires-Dist: {dep}\n"

        for url in sorted(meta.project_urls or {}):
            content += f"Project-URL: {url}, {meta.project_urls[url]}\n"

        if meta.long_description_content_type:
            content += "Description-Content-Type: {}\n".format(
                meta.long_description_content_type)
        if meta.long_description:
            readme = meta.long_description
            if full:
                content += "\n" + readme + "\n"
            else:
                content += "Description: {}\n".format(
                    textwrap.indent(readme, " " * 8,
                                    lambda line: True).lstrip())

        return content

    def ensure_setup_py(self, clean: bool = True) -> Path:
        """Ensures the requirement has a setup.py ready."""
        # XXX: Currently only handle PDM project, and do nothing if not.

        setup_py_path = self.location.joinpath("setup.py")
        if setup_py_path.is_file():
            return setup_py_path

        setup_py_path.write_text(self.format_setup_py(), encoding="utf-8")

        # Clean this temp file when process exits
        def cleanup() -> None:
            try:
                setup_py_path.unlink()
            except OSError:
                pass

        if clean:
            atexit.register(cleanup)
        return setup_py_path
Пример #20
0
def test_convert_legacy_project():
    metadata = Metadata(FIXTURES / "projects/demo-legacy/pyproject.toml")
    assert metadata.version == "0.1.0"
    assert metadata.dependencies == ["flask"]
    assert metadata.author == ""
    assert metadata.author_email == "frostming <*****@*****.**>"