Skip to content

Commit

Permalink
Add support for multiple README files
Browse files Browse the repository at this point in the history
This code reuses the `"readme"` entry in a way to allow the user to
declare in the old way or the new way.

With the changes, the two following declarations are valid:

**Single file**
```toml
readme = "README.rst"
```

**Multiple files**
```toml
readme = [
    "README.rst",
    "HISTORY.rst"
]
```

If the user declares files in different formats, the strict validation
will issue.

NOTICE:

The class `Package` suffered an important change: `readme` was renamed
to the plural `readmes`. Properties for the single form were introduced
to ensure retrocompatibility.
  • Loading branch information
wagnerluis1982 committed Nov 19, 2021
1 parent fa93845 commit eb8883e
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 15 deletions.
25 changes: 24 additions & 1 deletion src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ 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"])

package.description_type = cls._readme_content_type(package.readmes[0])

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 = [cls._readme_content_type(r) for r in config["readme"]]
if len(set(readme_types)) > 1:
result["errors"].append(
f"Declared README files must be of same type: found {', '.join(readme_types)}"
)

return result

@classmethod
Expand All @@ -441,3 +454,13 @@ def locate(cls, cwd: Optional[Path] = None) -> Path:
cwd
)
)

@staticmethod
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"
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
18 changes: 8 additions & 10 deletions src/poetry/core/masonry/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,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 +81,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.description_type:
meta.description_content_type = package.description_type

meta.provides_extra = list(package.extras)

Expand Down
24 changes: 23 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,8 @@ def __init__(
self.documentation_url = None
self.keywords = []
self._license = None
self.readme = None
self.readmes: Tuple[Path, ...] = ()
self.description_type: Optional[str] = None

self.extras = {}
self.requires_extras = []
Expand Down Expand Up @@ -347,6 +349,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
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 <[email protected]>"
]
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 <[email protected]>"]
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

0 comments on commit eb8883e

Please sign in to comment.