Skip to content

Commit

Permalink
factory: cleanup creation from package
Browse files Browse the repository at this point in the history
This change ensures that generated toml file from packages contain all
relevant metadata and handles groups correct.
  • Loading branch information
abn committed May 7, 2022
1 parent f645ab9 commit dfac6ee
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 19 deletions.
6 changes: 4 additions & 2 deletions src/poetry/console/commands/plugin/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ def handle(self) -> int:

break

root_package.python_versions = ".".join( # type: ignore[union-attr]
assert root_package is not None

root_package.python_versions = ".".join(
str(v) for v in system_env.version_info[:3]
)
# We create a `pyproject.toml` file based on all the information
# we have about the current environment.
if not env_dir.joinpath("pyproject.toml").exists():
Factory.create_pyproject_from_package(
root_package, # type: ignore[arg-type]
root_package,
env_dir,
)

Expand Down
107 changes: 96 additions & 11 deletions src/poetry/factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import contextlib
import logging

from typing import TYPE_CHECKING
Expand All @@ -22,10 +23,17 @@
from poetry.poetry import Poetry


try:
from poetry.core.packages.dependency_group import MAIN_GROUP
except ImportError:
MAIN_GROUP = "default"


if TYPE_CHECKING:
from pathlib import Path

from cleo.io.io import IO
from poetry.core.packages.package import Package

from poetry.repositories.legacy_repository import LegacyRepository

Expand Down Expand Up @@ -206,23 +214,73 @@ def create_legacy_repository(
)

@classmethod
def create_pyproject_from_package(cls, package: ProjectPackage, path: Path) -> None:
def create_pyproject_from_package(
cls, package: Package, path: Path | None = None
) -> TOMLDocument:
import tomlkit

from poetry.layouts.layout import POETRY_DEFAULT
pyproject: dict[str, Any] = tomlkit.document()

tool_table = tomlkit.table()
tool_table._is_super_table = True
pyproject["tool"] = tool_table

pyproject: dict[str, Any] = tomlkit.loads(POETRY_DEFAULT)
content = pyproject["tool"]["poetry"]
content: dict[str, Any] = tomlkit.table()
pyproject["tool"]["poetry"] = content

content["name"] = package.name
content["version"] = package.version.text
content["description"] = package.description
content["authors"] = package.authors
content["license"] = package.license.id if package.license else ""

if package.classifiers:
content["classifiers"] = package.classifiers

for key, attr in {
("documentation", "documentation_url"),
("repository", "repository_url"),
("homepage", "homepage"),
("maintainers", "maintainers"),
("keywords", "keywords"),
}:
value = getattr(package, attr, None)
if value:
content[key] = value

readmes = []

for readme in package.readmes:
readme_posix_path = readme.as_posix()

with contextlib.suppress(ValueError):
if package.root_dir:
readme_posix_path = readme.relative_to(package.root_dir).as_posix()

readmes.append(readme_posix_path)

if readmes:
content["readme"] = readmes

optional_dependencies = set()
extras_section = None

if package.extras:
extras_section = tomlkit.table()

dependency_section = content["dependencies"]
for extra in package.extras:
_dependencies = []
for dependency in package.extras[extra]:
_dependencies.append(dependency.name)
optional_dependencies.add(dependency.name)

extras_section[extra] = _dependencies

optional_dependencies = set(optional_dependencies)
dependency_section = content["dependencies"] = tomlkit.table()
dependency_section["python"] = package.python_versions

for dep in package.requires:
for dep in package.all_requires:
constraint: dict[str, Any] = tomlkit.inline_table()
if dep.is_vcs():
dep = cast(VCSDependency, dep)
Expand All @@ -241,12 +299,39 @@ def create_pyproject_from_package(cls, package: ProjectPackage, path: Path) -> N
if dep.extras:
constraint["extras"] = sorted(dep.extras)

if dep.name in optional_dependencies:
constraint["optional"] = True

if len(constraint) == 1 and "version" in constraint:
constraint = constraint["version"]

dependency_section[dep.name] = constraint
for group in dep.groups:
if group == MAIN_GROUP:
dependency_section[dep.name] = constraint
else:
if "group" not in content:
_table = tomlkit.table()
_table._is_super_table = True
content["group"] = _table

assert isinstance(pyproject, TOMLDocument)
path.joinpath("pyproject.toml").write_text(
pyproject.as_string(), encoding="utf-8"
)
if group not in content["group"]:
_table = tomlkit.table()
_table._is_super_table = True
content["group"][group] = _table

if "dependencies" not in content["group"][group]:
content["group"][group]["dependencies"] = tomlkit.table()

content["group"][group]["dependencies"][dep.name] = constraint

if extras_section:
content["extras"] = extras_section

pyproject.add(tomlkit.nl()) # type: ignore[attr-defined]

if path:
path.joinpath("pyproject.toml").write_text(
pyproject.as_string(), encoding="utf-8" # type: ignore[attr-defined]
)

return cast(TOMLDocument, pyproject)
4 changes: 2 additions & 2 deletions tests/fixtures/simple_project/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = [
]
license = "MIT"

readme = "README.rst"
readme = ["README.rst"]

homepage = "https://python-poetry.org"
repository = "https://github.com/python-poetry/poetry"
Expand All @@ -31,5 +31,5 @@ fox = "fuz.foo:bar.baz"


[build-system]
requires = ["poetry-core>=1.0.2"]
requires = ["poetry-core>=1.1.0a7"]
build-backend = "poetry.core.masonry.api"
39 changes: 35 additions & 4 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest

from deepdiff import DeepDiff
from entrypoints import EntryPoint
from poetry.core.semver.helpers import parse_constraint
from poetry.core.toml.file import TOMLFile
Expand Down Expand Up @@ -41,10 +42,12 @@ def test_create_poetry():
assert package.description == "Some description."
assert package.authors == ["Sébastien Eustace <sebastien@eustace.io>"]
assert package.license.id == "MIT"
assert (
package.readme.relative_to(fixtures_dir).as_posix()
== "sample_project/README.rst"
)

for readme in package.readmes:
assert (
readme.relative_to(fixtures_dir).as_posix() == "sample_project/README.rst"
)

assert package.homepage == "https://python-poetry.org"
assert package.repository_url == "https://github.com/python-poetry/poetry"
assert package.keywords == ["packaging", "dependency", "poetry"]
Expand Down Expand Up @@ -133,6 +136,34 @@ def test_create_poetry():
]


@pytest.mark.parametrize(
("project",),
[
("simple_project",),
("project_with_extras",),
],
)
def test_create_pyproject_from_package(project: str):
poetry = Factory().create_poetry(fixtures_dir / project)
package = poetry.package

pyproject = Factory.create_pyproject_from_package(package)

result = pyproject["tool"]["poetry"]
expected = poetry.pyproject.poetry_config

# packages do not support this at present
expected.pop("scripts", None)

# remove any empty sections
sections = list(expected.keys())
for section in sections:
if not expected[section]:
expected.pop(section)

assert not DeepDiff(expected, result)


def test_create_poetry_with_packages_and_includes():
poetry = Factory().create_poetry(fixtures_dir / "with-include")

Expand Down

0 comments on commit dfac6ee

Please sign in to comment.