Skip to content

Commit

Permalink
Introduce non-package-mode (#661)
Browse files Browse the repository at this point in the history
- add a new attribute `package-mode` in `tool.poetry`
- `package-mode` can be either `true` (default) or `false`
- in non-package mode metadata like `name` and `version` is not required
- building is not possible in non-package mode
  • Loading branch information
radoering authored Dec 20, 2023
1 parent fba71c1 commit 0199fb2
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 16 deletions.
25 changes: 22 additions & 3 deletions src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ def create_poetry(
raise RuntimeError("The Poetry configuration is invalid:\n" + message)

# Load package
name = local_config["name"]
# If name or version were missing in package mode, we would have already
# raised an error, so we can safely assume they might only be missing
# in non-package mode and use some dummy values in this case.
name = local_config.get("name", "non-package-mode")
assert isinstance(name, str)
version = local_config["version"]
version = local_config.get("version", "0")
assert isinstance(version, str)
package = self.get_package(name, version)
package = self.configure_package(
Expand Down Expand Up @@ -128,7 +131,7 @@ def configure_package(

package.root_dir = root

for author in config["authors"]:
for author in config.get("authors", []):
package.authors.append(combine_unicode(author))

for maintainer in config.get("maintainers", []):
Expand Down Expand Up @@ -389,6 +392,22 @@ def validate(
# Schema validation errors
validation_errors = validate_object(config, "poetry-schema")

# json validation may only say "data cannot be validated by any definition",
# which is quite vague, so we try to give a more precise error message
generic_error = "data cannot be validated by any definition"
if generic_error in validation_errors:
package_mode = config.get("package-mode", True)
if not isinstance(package_mode, bool):
validation_errors[validation_errors.index(generic_error)] = (
f"Invalid value for package-mode: {package_mode}"
)
elif package_mode:
required = {"name", "version", "description", "authors"}
if missing := required.difference(config):
validation_errors[validation_errors.index(generic_error)] = (
f"The fields {sorted(missing)} are required in package mode."
)

result["errors"] += validation_errors

if strict:
Expand Down
31 changes: 26 additions & 5 deletions src/poetry/core/json/schemas/poetry-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,34 @@
"name": "Package",
"type": "object",
"additionalProperties": true,
"required": [
"name",
"version",
"description",
"authors"
"anyOf": [
{
"required": [
"package-mode"
],
"properties": {
"package-mode": {
"enum": [
false
]
}
}
},
{
"required": [
"name",
"version",
"description",
"authors"
]
}
],
"properties": {
"package-mode": {
"type": "boolean",
"default": true,
"description": "Whether Poetry is operated in package mode or non-package mode."
},
"name": {
"type": "string",
"description": "Package name."
Expand Down
5 changes: 5 additions & 0 deletions src/poetry/core/masonry/builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def __init__(
) -> None:
from poetry.core.masonry.metadata import Metadata

if not poetry.is_package_mode:
raise RuntimeError(
"Building a package is not possible in non-package mode."
)

self._poetry = poetry
self._package = poetry.package
self._path: Path = poetry.pyproject_path.parent
Expand Down
6 changes: 6 additions & 0 deletions src/poetry/core/poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def pyproject_path(self) -> Path:
def package(self) -> ProjectPackage:
return self._package

@property
def is_package_mode(self) -> bool:
package_mode = self._local_config["package-mode"]
assert isinstance(package_mode, bool)
return package_mode

@property
def local_config(self) -> dict[str, Any]:
return self._local_config
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/invalid_mode/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.poetry]
package-mode = "invalid"
7 changes: 7 additions & 0 deletions tests/fixtures/non_package_mode/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool.poetry]
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"
cleo = "^0.6"
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
29 changes: 29 additions & 0 deletions tests/json/test_poetry_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ def multi_url_object() -> dict[str, Any]:
}


@pytest.mark.parametrize("explicit", [True, False])
@pytest.mark.parametrize(
"missing_required", ["", "name", "version", "description", "authors"]
)
def test_package_mode(
base_object: dict[str, Any], explicit: bool, missing_required: str
) -> None:
if explicit:
base_object["package-mode"] = True
if missing_required:
del base_object[missing_required]
assert len(validate_object(base_object, "poetry-schema")) == 1
else:
assert len(validate_object(base_object, "poetry-schema")) == 0


def test_non_package_mode_no_metadata() -> None:
assert len(validate_object({"package-mode": False}, "poetry-schema")) == 0


def test_non_package_mode_with_metadata(base_object: dict[str, Any]) -> None:
base_object["package-mode"] = False
assert len(validate_object(base_object, "poetry-schema")) == 0


def test_invalid_mode() -> None:
assert len(validate_object({"package-mode": "foo"}, "poetry-schema")) == 1


def test_path_dependencies(base_object: dict[str, Any]) -> None:
base_object["dependencies"].update({"foo": {"path": "../foo"}})
base_object["dev-dependencies"].update({"foo": {"path": "../foo"}})
Expand Down
17 changes: 14 additions & 3 deletions tests/masonry/builders/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
from pytest_mock import MockerFixture


def test_building_not_possible_in_non_package_mode() -> None:
with pytest.raises(RuntimeError) as err:
Builder(
Factory().create_poetry(
Path(__file__).parent.parent.parent / "fixtures" / "non_package_mode"
)
)

assert str(err.value) == "Building a package is not possible in non-package mode."


def test_builder_find_excluded_files(mocker: MockerFixture) -> None:
p = mocker.patch("poetry.core.vcs.git.Git.get_ignored_files")
p.return_value = []
Expand Down Expand Up @@ -190,7 +201,7 @@ def test_missing_script_files_throws_error() -> None:
with pytest.raises(RuntimeError) as err:
builder.convert_script_files()

assert "is not found." in err.value.args[0]
assert "is not found." in str(err.value)


def test_invalid_script_files_definition() -> None:
Expand All @@ -203,8 +214,8 @@ def test_invalid_script_files_definition() -> None:
)
)

assert "configuration is invalid" in err.value.args[0]
assert "scripts.invalid_definition" in err.value.args[0]
assert "configuration is invalid" in str(err.value)
assert "scripts.invalid_definition" in str(err.value)


@pytest.mark.parametrize(
Expand Down
11 changes: 11 additions & 0 deletions tests/masonry/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ def test_builder_factory_raises_error_when_format_is_not_valid() -> None:
Builder(get_poetry("complete")).build("not_valid")


@pytest.mark.parametrize("format", ["sdist", "wheel", "all"])
def test_builder_raises_error_in_non_package_mode(tmp_path: Path, format: str) -> None:
poetry = Factory().create_poetry(
Path(__file__).parent.parent / "fixtures" / "non_package_mode"
)
with pytest.raises(RuntimeError) as err:
Builder(poetry).build(format, target_dir=tmp_path)

assert str(err.value) == "Building a package is not possible in non-package mode."


@pytest.mark.parametrize("format", ["sdist", "wheel", "all"])
def test_builder_creates_places_built_files_in_specified_directory(
tmp_path: Path, format: str
Expand Down
31 changes: 26 additions & 5 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
def test_create_poetry() -> None:
poetry = Factory().create_poetry(fixtures_dir / "sample_project")

assert poetry.is_package_mode

package = poetry.package

assert package.name == "my-package"
Expand Down Expand Up @@ -237,6 +239,12 @@ def test_create_poetry_with_multi_constraints_dependency() -> None:
assert len(package.requires) == 2


def test_create_poetry_non_package_mode() -> None:
poetry = Factory().create_poetry(fixtures_dir / "non_package_mode")

assert not poetry.is_package_mode


def test_validate() -> None:
complete = fixtures_dir / "complete.toml"
with complete.open("rb") as f:
Expand Down Expand Up @@ -269,8 +277,8 @@ def test_validate_without_strict_fails_only_non_strict() -> None:
assert Factory.validate(content) == {
"errors": [
(
"data must contain ['authors', 'description', 'name', 'version'] "
"properties"
"The fields ['authors', 'description', 'name', 'version']"
" are required in package mode."
),
],
"warnings": [],
Expand All @@ -288,8 +296,8 @@ def test_validate_strict_fails_strict_and_non_strict() -> None:
assert Factory.validate(content, strict=True) == {
"errors": [
(
"data must contain ['authors', 'description', 'name', 'version']"
" properties"
"The fields ['authors', 'description', 'name', 'version']"
" are required in package mode."
),
(
'Cannot find dependency "missing_extra" for extra "some-extras" in '
Expand Down Expand Up @@ -354,7 +362,20 @@ def test_create_poetry_fails_on_invalid_configuration() -> None:

expected = """\
The Poetry configuration is invalid:
- data must contain ['description'] properties
- The fields ['description'] are required in package mode.
"""
assert str(e.value) == expected


def test_create_poetry_fails_on_invalid_mode() -> None:
with pytest.raises(RuntimeError) as e:
Factory().create_poetry(
Path(__file__).parent / "fixtures" / "invalid_mode" / "pyproject.toml"
)

expected = """\
The Poetry configuration is invalid:
- Invalid value for package-mode: invalid
"""
assert str(e.value) == expected

Expand Down

0 comments on commit 0199fb2

Please sign in to comment.