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
  • Loading branch information
finswimmer committed Aug 22, 2022
1 parent d2abf92 commit 24fe221
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 43 deletions.
6 changes: 5 additions & 1 deletion poetry/inspection/info.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import glob
import logging
import os
Expand Down Expand Up @@ -437,7 +438,10 @@ def _get_poetry_package(path): # type: (Path) -> Optional[ProjectPackage]
# 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
def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
Expand Down
47 changes: 26 additions & 21 deletions poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,34 +513,39 @@ def _install_directory(self, operation):
legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
19, 0, 0
)
package_poetry = Factory().create_poetry(pyproject.file.path.parent)

if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder
try:
package_poetry = Factory().create_poetry(pyproject.file.path.parent)
except RuntimeError:
package_poetry = None

# 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()
if package_poetry is not None:
if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder

return 0
elif legacy_pip or package_poetry.package.build_script:
from poetry.core.masonry.builders.sdist import SdistBuilder
# 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)
# 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:
args.append("-e")
with builder.setup_py():
if package.develop:
args.append("-e")

args.append(req)
args.append(req)

return self.run_pip(*args)
return self.run_pip(*args)

if package.develop:
args.append("-e")
Expand Down
47 changes: 26 additions & 21 deletions poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,34 +202,39 @@ def install_directory(self, package):
legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
19, 0, 0
)
package_poetry = Factory().create_poetry(pyproject.file.path.parent)

if package.develop and not package_poetry.package.build_script:
from poetry.masonry.builders.editable import EditableBuilder
try:
package_poetry = Factory().create_poetry(pyproject.file.path.parent)
except RuntimeError:
package_poetry = None

if package_poetry is not None:
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()
# 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
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)
# 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:
args.append("-e")
with builder.setup_py():
if package.develop:
args.append("-e")

args.append(req)
args.append(req)

return self.run(*args)
return self.run(*args)

if package.develop:
args.append("-e")
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 <[email protected]>"]

[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"
16 changes: 16 additions & 0 deletions tests/inspection/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@ def test_info_from_poetry_directory():
demo_check_info(info)


def test_info_from_poetry_directory_fallback_on_poetry_create_error(mocker):
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.PackageInfo._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
50 changes: 50 additions & 0 deletions tests/installation/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,53 @@ def test_executor_should_use_cached_link_and_hash(
)

assert archive == link_cached


def test_executer_fallback_on_poetry_create_error(
mocker, config, pool, io, tmp_dir, mock_file_downloads,
):
import poetry.installation.executor

mock_pip_install = mocker.patch.object(
poetry.installation.executor.Executor, "run_pip"
)
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})

env = MockEnv(path=Path(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 = """
Package operations: 1 install, 0 updates, 0 removals
• Installing simple-project (1.2.3 {source_url})
""".format(
source_url=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
32 changes: 32 additions & 0 deletions tests/installation/test_pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,35 @@ def copy_only(source, dest):
# any command in the virtual environment should trigger the error message
output = tmp_venv.run("python", "-m", "site")
assert "Error processing line 1 of {}".format(pth_file_candidate) not in output


def test_install_directory_fallback_on_poetry_create_error(mocker, tmp_venv, pool):
import poetry.installation.pip_installer

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.object(
poetry.installation.pip_installer.PipInstaller, "run"
)

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

0 comments on commit 24fe221

Please sign in to comment.