Commit 12610bac by Arun Babu Neelicattu Committed by GitHub

Handle inspection for package without dependencies and no setup.py files (#2615)

* inspection: improve handling of sdist packages

This change ensures that inspection does not fail pre-maturely if
source does not contain a setup.py and no dependencies were
discovered.

Resolves: #2603

* inspection: add tests for package info discovery
parent f6b06805
...@@ -176,10 +176,11 @@ class PackageInfo: ...@@ -176,10 +176,11 @@ class PackageInfo:
:param dist: The distribution instance to parse information from. :param dist: The distribution instance to parse information from.
""" """
requirements = None
if dist.requires_dist: if dist.requires_dist:
requirements = list(dist.requires_dist) requirements = list(dist.requires_dist)
else: else:
requirements = []
requires = Path(dist.filename) / "requires.txt" requires = Path(dist.filename) / "requires.txt"
if requires.exists(): if requires.exists():
with requires.open(encoding="utf-8") as f: with requires.open(encoding="utf-8") as f:
...@@ -190,7 +191,7 @@ class PackageInfo: ...@@ -190,7 +191,7 @@ class PackageInfo:
version=dist.version, version=dist.version,
summary=dist.summary, summary=dist.summary,
platform=dist.supported_platforms, platform=dist.supported_platforms,
requires_dist=requirements or None, requires_dist=requirements,
requires_python=dist.requires_python, requires_python=dist.requires_python,
) )
...@@ -234,8 +235,8 @@ class PackageInfo: ...@@ -234,8 +235,8 @@ class PackageInfo:
with temporary_directory() as tmp: with temporary_directory() as tmp:
tmp = Path(tmp) tmp = Path(tmp)
with context(str(path)) as archive: with context(path.as_posix()) as archive:
archive.extractall(str(tmp)) archive.extractall(tmp.as_posix())
# a little bit of guess work to determine the directory we care about # a little bit of guess work to determine the directory we care about
elements = list(tmp.glob("*")) elements = list(tmp.glob("*"))
...@@ -244,6 +245,8 @@ class PackageInfo: ...@@ -244,6 +245,8 @@ class PackageInfo:
sdist_dir = elements[0] sdist_dir = elements[0]
else: else:
sdist_dir = tmp / path.name.rstrip(suffix) sdist_dir = tmp / path.name.rstrip(suffix)
if not sdist_dir.is_dir():
sdist_dir = tmp
# now this is an unpacked directory we know how to deal with # now this is an unpacked directory we know how to deal with
new_info = cls.from_directory(path=sdist_dir) new_info = cls.from_directory(path=sdist_dir)
...@@ -313,7 +316,7 @@ class PackageInfo: ...@@ -313,7 +316,7 @@ class PackageInfo:
yield Path(d) yield Path(d)
@classmethod @classmethod
def from_metadata(cls, path): # type: (Union[str, Path]) -> PackageInfo def from_metadata(cls, path): # type: (Union[str, Path]) -> Optional[PackageInfo]
""" """
Helper method to parse package information from an unpacked metadata directory. Helper method to parse package information from an unpacked metadata directory.
...@@ -322,23 +325,31 @@ class PackageInfo: ...@@ -322,23 +325,31 @@ class PackageInfo:
path = Path(path) path = Path(path)
if path.suffix in {".dist-info", ".egg-info"}: if path.suffix in {".dist-info", ".egg-info"}:
directories = [path.suffix] directories = [path]
else: else:
directories = cls._find_dist_info(path=path) directories = cls._find_dist_info(path=path)
for directory in directories: for directory in directories:
try: try:
if directory.suffix == ".egg-info": if directory.suffix == ".egg-info":
dist = pkginfo.UnpackedSDist(str(directory)) dist = pkginfo.UnpackedSDist(directory.as_posix())
elif directory.suffix == ".dist-info": elif directory.suffix == ".dist-info":
dist = pkginfo.Wheel(str(directory)) dist = pkginfo.Wheel(directory.as_posix())
else: else:
continue continue
info = cls._from_distribution(dist=dist) break
if info:
return info
except ValueError: except ValueError:
pass continue
else:
try:
# handle PKG-INFO in unpacked sdist root
dist = pkginfo.UnpackedSDist(path.as_posix())
except ValueError:
return
info = cls._from_distribution(dist=dist)
if info:
return info
@classmethod @classmethod
def from_package(cls, package): # type: (Package) -> PackageInfo def from_package(cls, package): # type: (Package) -> PackageInfo
...@@ -355,7 +366,7 @@ class PackageInfo: ...@@ -355,7 +366,7 @@ class PackageInfo:
return cls( return cls(
name=package.name, name=package.name,
version=package.version, version=str(package.version),
summary=package.description, summary=package.description,
platform=package.platform, platform=package.platform,
requires_dist=list(requires), requires_dist=list(requires),
...@@ -395,6 +406,14 @@ class PackageInfo: ...@@ -395,6 +406,14 @@ class PackageInfo:
""" """
path = Path(path) path = Path(path)
current_dir = os.getcwd()
info = cls.from_metadata(path)
if info and info.requires_dist is not None:
# return only if requirements are discovered
return info
setup_py = path.joinpath("setup.py") setup_py = path.joinpath("setup.py")
project_package = cls._get_poetry_package(path) project_package = cls._get_poetry_package(path)
...@@ -402,17 +421,12 @@ class PackageInfo: ...@@ -402,17 +421,12 @@ class PackageInfo:
return cls.from_package(project_package) return cls.from_package(project_package)
if not setup_py.exists(): if not setup_py.exists():
if not allow_build and info:
# we discovered PkgInfo but no requirements were listed
return info
# this means we cannot do anything else here # this means we cannot do anything else here
raise PackageInfoError(path) raise PackageInfoError(path)
current_dir = os.getcwd()
info = cls.from_metadata(path)
if info and info.requires_dist is not None:
# return only if requirements are discovered
return info
if not allow_build: if not allow_build:
return cls.from_setup_py(path=path) return cls.from_setup_py(path=path)
......
[tool.poetry]
name = "demo"
version = "0.1.0"
description = ""
authors = ["Sébastien Eustace <sebastien@eustace.io>"]
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
pendulum = ">=1.4.4"
cleo = {version = "*", optional = true}
tomlkit = {version = "*", optional = true}
[tool.poetry.extras]
foo = ["cleo"]
bar = ["tomlkit"]
[tool.poetry.dev-dependencies]
pytest = "^3.0"
Metadata-Version: 1.0
Name: demo
Version: 0.1.0
Summary: Demo project.
Home-page: https://github.com/demo/demo
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
License: MIT
Description: UNKNOWN
Platform: UNKNOWN
# this was copied over and modified from orjson project's pyproject.toml
# https://github.com/ijl/orjson/blob/master/pyproject.toml
[project]
name = "demo"
repository = "https://github.com/demo/demo"
[build-system]
build-backend = "maturin"
requires = ["maturin>=0.8.1,<0.9"]
[tool.maturin]
manylinux = "off"
sdist-include = ["Cargo.lock", "json/**/*"]
strip = "on"
[tool.black]
line-length = 88
target-version = ['py36', 'py37', 'py38']
include = '\.pyi?$'
Metadata-Version: 1.0
Name: demo
Version: 0.1.0
Summary: Demo project.
Home-page: https://github.com/demo/demo
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
License: MIT
Description: UNKNOWN
Platform: UNKNOWN
cleo; extra == "foo"
pendulum (>=1.4.4)
tomlkit; extra == "bar"
# -*- coding: utf-8 -*-
from setuptools import setup
kwargs = dict(
name="demo",
license="MIT",
version="0.1.0",
description="Demo project.",
author="Sébastien Eustace",
author_email="sebastien@eustace.io",
url="https://github.com/demo/demo",
packages=["my_package"],
install_requires=[
'cleo; extra == "foo"',
"pendulum (>=1.4.4)",
'tomlkit; extra == "bar"',
],
)
setup(**kwargs)
import pytest
from poetry.inspection.info import PackageInfo
from poetry.utils._compat import PY35
from poetry.utils._compat import Path
FIXTURE_DIR_BASE = Path(__file__).parent.parent / "fixtures"
FIXTURE_DIR_INSPECTIONS = FIXTURE_DIR_BASE / "inspection"
@pytest.fixture
def demo_sdist(): # type: () -> Path
return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0.tar.gz"
@pytest.fixture
def demo_wheel(): # type: () -> Path
return FIXTURE_DIR_BASE / "distributions" / "demo-0.1.0-py2.py3-none-any.whl"
def demo_check_info(info): # type: (PackageInfo) -> None
assert info.name == "demo"
assert info.version == "0.1.0"
assert set(info.requires_dist) == {
'cleo; extra == "foo"',
"pendulum (>=1.4.4)",
'tomlkit; extra == "bar"',
}
def test_info_from_sdist(demo_sdist):
info = PackageInfo.from_sdist(demo_sdist)
demo_check_info(info)
def test_info_from_wheel(demo_wheel):
info = PackageInfo.from_wheel(demo_wheel)
demo_check_info(info)
def test_info_from_bdist(demo_wheel):
info = PackageInfo.from_bdist(demo_wheel)
demo_check_info(info)
def test_info_from_poetry_directory():
info = PackageInfo.from_directory(FIXTURE_DIR_INSPECTIONS / "demo")
demo_check_info(info)
def test_info_from_requires_txt():
info = PackageInfo.from_metadata(
FIXTURE_DIR_INSPECTIONS / "demo_only_requires_txt.egg-info"
)
demo_check_info(info)
@pytest.mark.skipif(not PY35, reason="Parsing of setup.py is skipped for Python < 3.5")
def test_info_from_setup_py():
info = PackageInfo.from_setup_py(FIXTURE_DIR_INSPECTIONS / "demo_only_setup")
demo_check_info(info)
def test_info_no_setup_pkg_info_no_deps():
info = PackageInfo.from_directory(
FIXTURE_DIR_INSPECTIONS / "demo_no_setup_pkg_info_no_deps"
)
assert info.name == "demo"
assert info.version == "0.1.0"
assert info.requires_dist is None
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment