Skip to content

Commit

Permalink
feat: fallback to gather metadata via pep517 if reading as Poetry pro…
Browse files Browse the repository at this point in the history
…ject raises RuntimeError (#5834)

* feat: fallback to gather metadata via pep517 if reading as Poetry project raises RuntimeError

* test: added tests for fallback to gather metadata via pep517
  • Loading branch information
finswimmer committed Aug 22, 2022
1 parent 8df4614 commit e8c7e9e
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 57 deletions.
4 changes: 3 additions & 1 deletion src/poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,9 @@ def _get_poetry_package(path: Path) -> ProjectPackage | None:
# Note: we ignore any setup.py file at this step
# TODO: add support for handling non-poetry PEP-517 builds
if PyProjectTOML(path.joinpath("pyproject.toml")).is_poetry_project():
return Factory().create_poetry(path).package
with contextlib.suppress(RuntimeError):
return Factory().create_poetry(path).package

return None

@classmethod
Expand Down
55 changes: 30 additions & 25 deletions src/poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,32 +553,37 @@ def _install_directory(self, operation: Install | Update) -> int:
self._env.pip_version
< self._env.pip_version.__class__.from_parts(19, 0, 0)
)
package_poetry = Factory().create_poetry(pyproject.file.path.parent)

builder: Builder
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder

# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()

return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder

# We need to rely on creating a temporary setup.py
# file since the version of pip does not support
# build-systems
# We also need it for non-PEP-517 packages
builder = SdistBuilder(package_poetry)

with builder.setup_py():
if package.develop:
return self.pip_install(req, upgrade=True, editable=True)
return self.pip_install(req, upgrade=True)
try:
package_poetry = Factory().create_poetry(pyproject.file.path.parent)
except RuntimeError:
package_poetry = None

if package_poetry is not None:
builder: Builder
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder

# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()

return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder

# We need to rely on creating a temporary setup.py
# file since the version of pip does not support
# build-systems
# We also need it for non-PEP-517 packages
builder = SdistBuilder(package_poetry)

with builder.setup_py():
if package.develop:
return self.pip_install(req, upgrade=True, editable=True)
return self.pip_install(req, upgrade=True)

if package.develop:
return self.pip_install(req, upgrade=True, editable=True)
Expand Down
67 changes: 36 additions & 31 deletions src/poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,39 +231,44 @@ def install_directory(self, package: Package) -> str | int:
# so we need to check the version of pip to know
# if we can rely on the build system
legacy_pip = self._env.pip_version < Version.from_parts(19, 0, 0)
package_poetry = Factory().create_poetry(pyproject.file.path.parent)

builder: Builder
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder

# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()

return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder

# We need to rely on creating a temporary setup.py
# file since the version of pip does not support
# build-systems
# We also need it for non-PEP-517 packages
builder = SdistBuilder(package_poetry)

with builder.setup_py():
if package.develop:

try:
package_poetry = Factory().create_poetry(pyproject.file.path.parent)
except RuntimeError:
package_poetry = None

if package_poetry is not None:
builder: Builder
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder

# This is a Poetry package in editable mode
# we can use the EditableBuilder without going through pip
# to install it, unless it has a build script.
builder = EditableBuilder(package_poetry, self._env, NullIO())
builder.build()

return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder

# We need to rely on creating a temporary setup.py
# file since the version of pip does not support
# build-systems
# We also need it for non-PEP-517 packages
builder = SdistBuilder(package_poetry)

with builder.setup_py():
if package.develop:
return pip_install(
path=req,
environment=self._env,
upgrade=True,
editable=True,
)
return pip_install(
path=req,
environment=self._env,
upgrade=True,
editable=True,
path=req, environment=self._env, deps=False, upgrade=True
)
return pip_install(
path=req, environment=self._env, deps=False, upgrade=True
)

if package.develop:
return pip_install(
Expand Down
15 changes: 15 additions & 0 deletions tests/fixtures/inspection/demo_poetry_package/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.poetry]
name = "demo-poetry"
version = "0.1.0"
description = ""
authors = ["John Doe <john@example.com.com>"]

[tool.poetry.dependencies]
python = "^3.10"
pendulum = "*"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
18 changes: 18 additions & 0 deletions tests/inspection/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,24 @@ def test_info_from_poetry_directory():
demo_check_info(info)


def test_info_from_poetry_directory_fallback_on_poetry_create_error(
mocker: MockerFixture,
):
mock_create_poetry = mocker.patch(
"poetry.inspection.info.Factory.create_poetry", side_effect=RuntimeError
)
mock_get_poetry_package = mocker.spy(PackageInfo, "_get_poetry_package")
mock_get_pep517_metadata = mocker.patch(
"poetry.inspection.info.get_pep517_metadata"
)

PackageInfo.from_directory(FIXTURE_DIR_INSPECTIONS / "demo_poetry_package")

assert mock_create_poetry.call_count == 1
assert mock_get_poetry_package.call_count == 1
assert mock_get_pep517_metadata.call_count == 1


def test_info_from_requires_txt():
info = PackageInfo.from_metadata(
FIXTURE_DIR_INSPECTIONS / "demo_only_requires_txt.egg-info"
Expand Down
56 changes: 56 additions & 0 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,59 @@ def test_executor_should_be_initialized_with_correct_workers(
executor = Executor(tmp_venv, pool, config, io)

assert executor._max_workers == expected_workers


def test_executer_fallback_on_poetry_create_error(
mocker: MockerFixture,
config: Config,
pool: Pool,
io: BufferedIO,
tmp_dir: str,
mock_file_downloads: None,
env: MockEnv,
):
mock_pip_install = mocker.patch("poetry.installation.executor.pip_install")
mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder")
mock_editable_builder = mocker.patch(
"poetry.masonry.builders.editable.EditableBuilder"
)
mock_create_poetry = mocker.patch(
"poetry.factory.Factory.create_poetry", side_effect=RuntimeError
)

config.merge({"cache-dir": tmp_dir})

executor = Executor(env, pool, config, io)

directory_package = Package(
"simple-project",
"1.2.3",
source_type="directory",
source_url=Path(__file__)
.parent.parent.joinpath("fixtures/simple_project")
.resolve()
.as_posix(),
)

return_code = executor.execute(
[
Install(directory_package),
]
)

expected = f"""
Package operations: 1 install, 0 updates, 0 removals
• Installing simple-project (1.2.3 {directory_package.source_url})
"""

expected = set(expected.splitlines())
output = set(io.fetch_output().splitlines())
assert output == expected
assert return_code == 0
assert mock_create_poetry.call_count == 1
assert mock_sdist_builder.call_count == 0
assert mock_editable_builder.call_count == 0
assert mock_pip_install.call_count == 1
assert mock_pip_install.call_args[1].get("upgrade") is True
assert mock_pip_install.call_args[1].get("editable") is False
32 changes: 32 additions & 0 deletions tests/installation/test_pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,35 @@ def test_install_with_trusted_host(config: Config):
assert "--trusted-host" in cmd
cert_index = cmd.index("--trusted-host")
assert cmd[cert_index + 1] == "foo.bar"


def test_install_directory_fallback_on_poetry_create_error(
mocker: MockerFixture, tmp_venv: VirtualEnv, pool: Pool
):
mock_create_poetry = mocker.patch(
"poetry.factory.Factory.create_poetry", side_effect=RuntimeError
)
mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder")
mock_editable_builder = mocker.patch(
"poetry.masonry.builders.editable.EditableBuilder"
)
mock_pip_install = mocker.patch("poetry.installation.pip_installer.pip_install")

package = Package(
"demo",
"1.0.0",
source_type="directory",
source_url=str(
Path(__file__).parent.parent / "fixtures/inspection/demo_poetry_package"
),
)

installer = PipInstaller(tmp_venv, NullIO(), pool)
installer.install_directory(package)

assert mock_create_poetry.call_count == 1
assert mock_sdist_builder.call_count == 0
assert mock_editable_builder.call_count == 0
assert mock_pip_install.call_count == 1
assert mock_pip_install.call_args[1].get("deps") is False
assert mock_pip_install.call_args[1].get("upgrade") is True

0 comments on commit e8c7e9e

Please sign in to comment.