Skip to content

Commit f101162

Browse files
Add support for multiple README files (#118)
Closes python-poetry/poetry#873
1 parent 948ee78 commit f101162

10 files changed

Lines changed: 107 additions & 15 deletions

File tree

poetry/core/factory.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,12 @@ def configure_package(
9494
package.classifiers = config.get("classifiers", [])
9595

9696
if "readme" in config:
97-
package.readme = root / config["readme"]
97+
if isinstance(config["readme"], str):
98+
package.readmes = (root / config["readme"],)
99+
else:
100+
package.readmes = tuple(root / readme for readme in config["readme"])
101+
102+
package.description_type = cls._readme_content_type(package.readmes[0])
98103

99104
if "platform" in config:
100105
package.platform = config["platform"]
@@ -419,6 +424,14 @@ def validate(cls, config: dict, strict: bool = False) -> Dict[str, List[str]]:
419424
)
420425
)
421426

427+
# Checking types of all readme files (must match)
428+
if "readme" in config and not isinstance(config["readme"], str):
429+
readme_types = [cls._readme_content_type(r) for r in config["readme"]]
430+
if len(set(readme_types)) > 1:
431+
result["errors"].append(
432+
f"Declared README files must be of same type: found {', '.join(readme_types)}"
433+
)
434+
422435
return result
423436

424437
@classmethod
@@ -438,3 +451,13 @@ def locate(cls, cwd: Path) -> Path:
438451
cwd
439452
)
440453
)
454+
455+
@staticmethod
456+
def _readme_content_type(path: Union[str, Path]) -> str:
457+
suffix = Path(path).suffix
458+
if suffix == ".rst":
459+
return "text/x-rst"
460+
elif suffix in [".md", ".markdown"]:
461+
return "text/markdown"
462+
else:
463+
return "text/plain"

poetry/core/json/schemas/poetry-schema.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,19 @@
5555
"$ref": "#/definitions/maintainers"
5656
},
5757
"readme": {
58-
"type": "string",
59-
"description": "The path to the README file"
58+
"anyOf": [
59+
{
60+
"type": "string",
61+
"description": "The path to the README file."
62+
},
63+
{
64+
"type": "array",
65+
"description": "A list of paths to the readme files.",
66+
"items": {
67+
"type": "string"
68+
}
69+
}
70+
]
6071
},
6172
"classifiers": {
6273
"type": "array",

poetry/core/masonry/metadata.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,12 @@ def from_package(cls, package: "Package") -> "Metadata":
5151
meta.name = canonicalize_name(package.name)
5252
meta.version = normalize_version(package.version.text)
5353
meta.summary = package.description
54-
if package.readme:
55-
with package.readme.open(encoding="utf-8") as f:
56-
meta.description = f.read()
54+
if package.readmes:
55+
descriptions = []
56+
for readme in package.readmes:
57+
with readme.open(encoding="utf-8") as f:
58+
descriptions.append(f.read())
59+
meta.description = "\n".join(descriptions)
5760

5861
meta.keywords = ",".join(package.keywords)
5962
meta.home_page = package.homepage or package.repository_url
@@ -76,13 +79,8 @@ def from_package(cls, package: "Package") -> "Metadata":
7679
meta.requires_dist = [d.to_pep_508() for d in package.requires]
7780

7881
# Version 2.1
79-
if package.readme:
80-
if package.readme.suffix == ".rst":
81-
meta.description_content_type = "text/x-rst"
82-
elif package.readme.suffix in [".md", ".markdown"]:
83-
meta.description_content_type = "text/markdown"
84-
else:
85-
meta.description_content_type = "text/plain"
82+
if package.description_type:
83+
meta.description_content_type = package.description_type
8684

8785
meta.provides_extra = [e for e in package.extras]
8886

poetry/core/packages/package.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ def __init__(
8888
self.documentation_url = None
8989
self.keywords = []
9090
self._license = None
91-
self.readme = None
91+
self.readmes = ()
92+
self.description_type = None
9293

9394
self.extras = {}
9495
self.requires_extras = []
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Single Python
2+
=============
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Changelog
2+
=========
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[tool.poetry]
2+
name = "single-python"
3+
version = "0.1"
4+
description = "Some description."
5+
authors = [
6+
"Wagner Macedo <wagnerluis1982@gmail.com>"
7+
]
8+
license = "MIT"
9+
10+
readme = [
11+
"README-1.rst",
12+
"README-2.rst"
13+
]
14+
15+
homepage = "https://python-poetry.org/"
16+
17+
18+
[tool.poetry.dependencies]
19+
python = "2.7.15"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Example module"""
2+
3+
__version__ = "0.1"

tests/masonry/builders/test_builder.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,16 @@ def test_builder_convert_script_files(fixture, result):
260260
project_root = Path(__file__).parent / "fixtures" / fixture
261261
script_files = Builder(Factory().create_poetry(project_root)).convert_script_files()
262262
assert [p.relative_to(project_root) for p in script_files] == result
263+
264+
265+
def test_metadata_with_readme_files():
266+
test_path = Path(__file__).parent.parent.parent / "fixtures" / "with_readme_files"
267+
builder = Builder(Factory().create_poetry(test_path))
268+
269+
metadata = Parser().parsestr(builder.get_metadata_content())
270+
271+
readme1 = test_path / "README-1.rst"
272+
readme2 = test_path / "README-2.rst"
273+
description = "\n".join([readme1.read_text(), readme2.read_text(), ""])
274+
275+
assert metadata.get_payload() == description

tests/test_factory.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_create_poetry():
2323
assert package.authors == ["Sébastien Eustace <sebastien@eustace.io>"]
2424
assert package.license.id == "MIT"
2525
assert (
26-
package.readme.relative_to(fixtures_dir).as_posix()
26+
package.readmes[0].relative_to(fixtures_dir).as_posix()
2727
== "sample_project/README.rst"
2828
)
2929
assert package.homepage == "https://python-poetry.org"
@@ -185,6 +185,26 @@ def test_validate_fails():
185185
assert Factory.validate(content) == {"errors": [expected], "warnings": []}
186186

187187

188+
def test_validate_multiple_readme_files():
189+
with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml")
190+
content = with_readme_files.read()["tool"]["poetry"]
191+
192+
assert Factory.validate(content, strict=True) == {"errors": [], "warnings": []}
193+
194+
195+
def test_validate_fails_on_readme_files_with_unmatching_types():
196+
with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml")
197+
content = with_readme_files.read()["tool"]["poetry"]
198+
content["readme"][0] = "README.md"
199+
200+
assert Factory.validate(content, strict=True) == {
201+
"errors": [
202+
"Declared README files must be of same type: found text/markdown, text/x-rst"
203+
],
204+
"warnings": [],
205+
}
206+
207+
188208
def test_create_poetry_fails_on_invalid_configuration():
189209
with pytest.raises(RuntimeError) as e:
190210
Factory().create_poetry(

0 commit comments

Comments
 (0)