Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from typing import Union
from warnings import warn

from poetry.core.utils.helpers import readme_content_type


if TYPE_CHECKING:
from poetry.core.packages.project_package import ProjectPackage
Expand Down Expand Up @@ -93,7 +95,10 @@ def configure_package(
package.classifiers = config.get("classifiers", [])

if "readme" in config:
package.readme = root / config["readme"]
if isinstance(config["readme"], str):
package.readmes = (root / config["readme"],)
else:
package.readmes = tuple(root / readme for readme in config["readme"])

if "platform" in config:
package.platform = config["platform"]
Expand Down Expand Up @@ -421,6 +426,14 @@ def validate(cls, config: dict, strict: bool = False) -> Dict[str, List[str]]:
)
)

# Checking types of all readme files (must match)
if "readme" in config and not isinstance(config["readme"], str):
readme_types = {readme_content_type(r) for r in config["readme"]}
if len(readme_types) > 1:
result["errors"].append(
f"Declared README files must be of same type: found {', '.join(sorted(readme_types))}"
)

return result

@classmethod
Expand Down
15 changes: 13 additions & 2 deletions src/poetry/core/json/schemas/poetry-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,19 @@
"$ref": "#/definitions/maintainers"
},
"readme": {
"type": "string",
"description": "The path to the README file"
"anyOf": [
{
"type": "string",
"description": "The path to the README file."
},
{
"type": "array",
"description": "A list of paths to the readme files.",
"items": {
"type": "string"
}
}
]
},
"classifiers": {
"type": "array",
Expand Down
20 changes: 10 additions & 10 deletions src/poetry/core/masonry/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from typing import List
from typing import Tuple

from poetry.core.utils.helpers import readme_content_type


if TYPE_CHECKING:
from poetry.core.packages.package import Package
Expand Down Expand Up @@ -53,9 +55,12 @@ def from_package(cls, package: "Package") -> "Metadata":
meta.name = canonicalize_name(package.name)
meta.version = normalize_version(package.version.text)
meta.summary = package.description
if package.readme:
with package.readme.open(encoding="utf-8") as f:
meta.description = f.read()
if package.readmes:
descriptions = []
for readme in package.readmes:
with readme.open(encoding="utf-8") as f:
descriptions.append(f.read())
meta.description = "\n".join(descriptions)

meta.keywords = ",".join(package.keywords)
meta.home_page = package.homepage or package.repository_url
Expand All @@ -78,13 +83,8 @@ def from_package(cls, package: "Package") -> "Metadata":
meta.requires_dist = [d.to_pep_508() for d in package.requires]

# Version 2.1
if package.readme:
if package.readme.suffix == ".rst":
meta.description_content_type = "text/x-rst"
elif package.readme.suffix in [".md", ".markdown"]:
meta.description_content_type = "text/markdown"
else:
meta.description_content_type = "text/plain"
if package.readmes:
meta.description_content_type = readme_content_type(package.readmes[0])

meta.provides_extra = list(package.extras)

Expand Down
23 changes: 22 additions & 1 deletion src/poetry/core/packages/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union

from poetry.core.packages.specification import PackageSpecification
Expand Down Expand Up @@ -87,7 +88,7 @@ def __init__(
self.documentation_url = None
self.keywords = []
self._license = None
self.readme = None
self.readmes: Tuple[Path, ...] = ()

self.extras = {}
self.requires_extras = []
Expand Down Expand Up @@ -347,6 +348,26 @@ def urls(self) -> Dict[str, str]:

return urls

@property
def readme(self) -> Path:
import warnings

warnings.warn(
"`readme` is deprecated: you are getting only the first readme file. Please use the plural form `readmes`.",
DeprecationWarning,
)
return next(iter(self.readmes), None)

@readme.setter
def readme(self, path: Path) -> None:
import warnings

warnings.warn(
"`readme` is deprecated. Please assign a tuple to the plural form `readmes`.",
DeprecationWarning,
)
self.readmes = (path,)

def is_prerelease(self) -> bool:
return self._version.is_unstable()

Expand Down
10 changes: 10 additions & 0 deletions src/poetry/core/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,13 @@ def merge_dicts(d1: dict, d2: dict) -> None:
merge_dicts(d1[k], d2[k])
else:
d1[k] = d2[k]


def readme_content_type(path: Union[str, Path]) -> str:
suffix = Path(path).suffix
if suffix == ".rst":
return "text/x-rst"
elif suffix in [".md", ".markdown"]:
return "text/markdown"
else:
return "text/plain"
2 changes: 2 additions & 0 deletions tests/fixtures/with_readme_files/README-1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Single Python
=============
2 changes: 2 additions & 0 deletions tests/fixtures/with_readme_files/README-2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Changelog
=========
19 changes: 19 additions & 0 deletions tests/fixtures/with_readme_files/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "single-python"
version = "0.1"
description = "Some description."
authors = [
"Wagner Macedo <wagnerluis1982@gmail.com>"
]
license = "MIT"

readme = [
"README-1.rst",
"README-2.rst"
]

homepage = "https://python-poetry.org/"


[tool.poetry.dependencies]
python = "2.7.15"
3 changes: 3 additions & 0 deletions tests/fixtures/with_readme_files/single_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Example module"""

__version__ = "0.1"
13 changes: 13 additions & 0 deletions tests/masonry/builders/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,16 @@ def test_builder_convert_script_files(fixture: str, result: List[Path]):
project_root = Path(__file__).parent / "fixtures" / fixture
script_files = Builder(Factory().create_poetry(project_root)).convert_script_files()
assert [p.relative_to(project_root) for p in script_files] == result


def test_metadata_with_readme_files():
test_path = Path(__file__).parent.parent.parent / "fixtures" / "with_readme_files"
builder = Builder(Factory().create_poetry(test_path))

metadata = Parser().parsestr(builder.get_metadata_content())

readme1 = test_path / "README-1.rst"
readme2 = test_path / "README-2.rst"
description = "\n".join([readme1.read_text(), readme2.read_text(), ""])

assert metadata.get_payload() == description
19 changes: 19 additions & 0 deletions tests/packages/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,22 @@ def test_only_with_dependency_groups(package_with_groups: Package):

assert len(package.requires) == 2
assert len(package.all_requires) == 2


def test_get_readme_property_with_multiple_readme_files():
package = Package("foo", "0.1.0")

package.readmes = ("README.md", "HISTORY.md")
with pytest.deprecated_call():
assert package.readme == "README.md"


def test_set_readme_property():
package = Package("foo", "0.1.0")

with pytest.deprecated_call():
package.readme = "README.md"

assert package.readmes == ("README.md",)
with pytest.deprecated_call():
assert package.readme == "README.md"
22 changes: 21 additions & 1 deletion tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_create_poetry():
assert package.authors == ["Sébastien Eustace <sebastien@eustace.io>"]
assert package.license.id == "MIT"
assert (
package.readme.relative_to(fixtures_dir).as_posix()
package.readmes[0].relative_to(fixtures_dir).as_posix()
== "sample_project/README.rst"
)
assert package.homepage == "https://python-poetry.org"
Expand Down Expand Up @@ -182,6 +182,26 @@ def test_validate_fails():
assert Factory.validate(content) == {"errors": [expected], "warnings": []}


def test_strict_validation_success_on_multiple_readme_files():
with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml")
content = with_readme_files.read()["tool"]["poetry"]

assert Factory.validate(content, strict=True) == {"errors": [], "warnings": []}


def test_strict_validation_fails_on_readme_files_with_unmatching_types():
with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml")
content = with_readme_files.read()["tool"]["poetry"]
content["readme"][0] = "README.md"

assert Factory.validate(content, strict=True) == {
"errors": [
"Declared README files must be of same type: found text/markdown, text/x-rst"
],
"warnings": [],
}


def test_create_poetry_fails_on_invalid_configuration():
with pytest.raises(RuntimeError) as e:
Factory().create_poetry(
Expand Down
18 changes: 18 additions & 0 deletions tests/utils/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os

from pathlib import Path
from stat import S_IREAD
from typing import Union

import pytest

from poetry.core.utils.helpers import canonicalize_name
from poetry.core.utils.helpers import parse_requires
from poetry.core.utils.helpers import readme_content_type
from poetry.core.utils.helpers import temporary_directory


Expand Down Expand Up @@ -76,3 +79,18 @@ def test_utils_helpers_temporary_directory_readonly_file():

assert not os.path.exists(temp_dir)
assert not os.path.exists(readonly_filename)


@pytest.mark.parametrize(
"readme, content_type",
[
("README.rst", "text/x-rst"),
("README.md", "text/markdown"),
("README", "text/plain"),
(Path("README.rst"), "text/x-rst"),
(Path("README.md"), "text/markdown"),
(Path("README"), "text/plain"),
],
)
def test_utils_helpers_readme_content_type(readme: Union[str, Path], content_type: str):
assert readme_content_type(readme) == content_type