Skip to content

Commit

Permalink
Fix metadata inconsistency between wheels and sdists
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Apr 7, 2019
1 parent e2ca061 commit 17a5df2
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 160 deletions.
56 changes: 56 additions & 0 deletions poetry/masonry/builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from poetry.utils._compat import basestring
from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache
from poetry.utils._compat import to_str
from poetry.vcs import get_vcs

from ..metadata import Metadata
Expand All @@ -22,6 +23,13 @@

AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+) <(?P<email>.+?)>$")

METADATA_BASE = """\
Metadata-Version: 2.1
Name: {name}
Version: {version}
Summary: {summary}
"""


class Builder(object):

Expand Down Expand Up @@ -147,6 +155,54 @@ def find_files_to_add(self, exclude_build=True): # type: (bool) -> list

return sorted(to_add)

def get_metadata_content(self): # type: () -> bytes
content = METADATA_BASE.format(
name=self._meta.name,
version=self._meta.version,
summary=to_str(self._meta.summary),
)

# Optional fields
if self._meta.home_page:
content += "Home-page: {}\n".format(self._meta.home_page)

if self._meta.license:
content += "License: {}\n".format(self._meta.license)

if self._meta.keywords:
content += "Keywords: {}\n".format(self._meta.keywords)

if self._meta.author:
content += "Author: {}\n".format(to_str(self._meta.author))

if self._meta.author_email:
content += "Author-email: {}\n".format(to_str(self._meta.author_email))

if self._meta.requires_python:
content += "Requires-Python: {}\n".format(self._meta.requires_python)

for classifier in self._meta.classifiers:
content += "Classifier: {}\n".format(classifier)

for extra in sorted(self._meta.provides_extra):
content += "Provides-Extra: {}\n".format(extra)

for dep in sorted(self._meta.requires_dist):
content += "Requires-Dist: {}\n".format(dep)

for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
content += "Project-URL: {}\n".format(to_str(url))

if self._meta.description_content_type:
content += "Description-Content-Type: {}\n".format(
self._meta.description_content_type
)

if self._meta.description is not None:
content += "\n" + to_str(self._meta.description) + "\n"

return content

def convert_entry_points(self): # type: () -> dict
result = defaultdict(list)

Expand Down
40 changes: 1 addition & 39 deletions poetry/masonry/builders/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,6 @@
"""


PKG_INFO = """\
Metadata-Version: 2.1
Name: {name}
Version: {version}
Summary: {summary}
Home-page: {home_page}
Author: {author}
Author-email: {author_email}
"""


class SdistBuilder(Builder):
def build(self, target_dir=None): # type: (Path) -> Path
self._io.writeln(" - Building <info>sdist</info>")
Expand Down Expand Up @@ -195,34 +184,7 @@ def build_setup(self): # type: () -> bytes
)

def build_pkg_info(self):
pkg_info = PKG_INFO.format(
name=self._meta.name,
version=self._meta.version,
summary=self._meta.summary,
home_page=self._meta.home_page,
author=to_str(self._meta.author),
author_email=to_str(self._meta.author_email),
)

if self._meta.keywords:
pkg_info += "Keywords: {}\n".format(self._meta.keywords)

if self._meta.requires_python:
pkg_info += "Requires-Python: {}\n".format(self._meta.requires_python)

for classifier in self._meta.classifiers:
pkg_info += "Classifier: {}\n".format(classifier)

for extra in sorted(self._meta.provides_extra):
pkg_info += "Provides-Extra: {}\n".format(extra)

for dep in sorted(self._meta.requires_dist):
pkg_info += "Requires-Dist: {}\n".format(dep)

for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
pkg_info += "Project-URL: {}\n".format(url)

return encode(pkg_info)
return encode(self.get_metadata_content())

def find_packages(self, include):
"""
Expand Down
42 changes: 2 additions & 40 deletions poetry/masonry/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from poetry.__version__ import __version__
from poetry.semver import parse_constraint
from poetry.utils._compat import decode

from ..utils.helpers import normalize_file_permissions
from ..utils.package_include import PackageInclude
Expand Down Expand Up @@ -309,43 +310,4 @@ def _write_metadata_file(self, fp):
"""
Write out metadata in the 2.x format (email like)
"""
fp.write("Metadata-Version: 2.1\n")
fp.write("Name: {}\n".format(self._meta.name))
fp.write("Version: {}\n".format(self._meta.version))
fp.write("Summary: {}\n".format(self._meta.summary))
fp.write("Home-page: {}\n".format(self._meta.home_page or "UNKNOWN"))
fp.write("License: {}\n".format(self._meta.license or "UNKNOWN"))

# Optional fields
if self._meta.keywords:
fp.write("Keywords: {}\n".format(self._meta.keywords))

if self._meta.author:
fp.write("Author: {}\n".format(self._meta.author))

if self._meta.author_email:
fp.write("Author-email: {}\n".format(self._meta.author_email))

if self._meta.requires_python:
fp.write("Requires-Python: {}\n".format(self._meta.requires_python))

for classifier in self._meta.classifiers:
fp.write("Classifier: {}\n".format(classifier))

for extra in sorted(self._meta.provides_extra):
fp.write("Provides-Extra: {}\n".format(extra))

for dep in sorted(self._meta.requires_dist):
fp.write("Requires-Dist: {}\n".format(dep))

for url in sorted(self._meta.project_urls, key=lambda u: u[0]):
fp.write("Project-URL: {}\n".format(url))

if self._meta.description_content_type:
fp.write(
"Description-Content-Type: "
"{}\n".format(self._meta.description_content_type)
)

if self._meta.description is not None:
fp.write("\n" + self._meta.description + "\n")
fp.write(decode(self.get_metadata_content()))
3 changes: 0 additions & 3 deletions tests/masonry/builders/fixtures/simple_version/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ description = "Some description."
authors = [
"Sébastien Eustace <[email protected]>"
]
license = "MIT"

readme = "README.rst"

homepage = "https://poetry.eustace.io/"


[tool.poetry.dependencies]
python = "3.6"
79 changes: 79 additions & 0 deletions tests/masonry/builders/test_builder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
from email.parser import Parser

from poetry.io import NullIO
from poetry.masonry.builders.builder import Builder
from poetry.poetry import Poetry
Expand Down Expand Up @@ -52,3 +55,79 @@ def test_builder_find_invalid_case_sensitive_excluded_files(mocker):
)

assert {"my_package/Bar/foo/bar/Foo.py"} == builder.find_excluded_files()


def test_get_metadata_content():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "complete"),
NullEnv(),
NullIO(),
)

metadata = builder.get_metadata_content()

p = Parser()
parsed = p.parsestr(metadata)

assert parsed["Metadata-Version"] == "2.1"
assert parsed["Name"] == "my-package"
assert parsed["Version"] == "1.2.3"
assert parsed["Summary"] == "Some description."
assert parsed["Author"] == "Sébastien Eustace"
assert parsed["Author-email"] == "[email protected]"
assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0"
assert parsed["License"] == "MIT"
assert parsed["Home-page"] == "https://poetry.eustace.io/"

classifiers = parsed.get_all("Classifier")
assert classifiers == [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]

extras = parsed.get_all("Provides-Extra")
assert extras == ["time"]

requires = parsed.get_all("Requires-Dist")
assert requires == [
"cachy[msgpack] (>=0.2.0,<0.3.0)",
"cleo (>=0.6,<0.7)",
'pendulum (>=1.4,<2.0); extra == "time"',
]

urls = parsed.get_all("Project-URL")
assert urls == [
"Documentation, https://poetry.eustace.io/docs",
"Repository, https://github.com/sdispater/poetry",
]


def test_metadata_homepage_default():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "simple_version"),
NullEnv(),
NullIO(),
)

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

assert metadata["Home-page"] is None


def test_metadata_with_vcs_dependencies():
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "with_vcs_dependency"),
NullEnv(),
NullIO(),
)

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

requires_dist = metadata["Requires-Dist"]

assert "cleo @ git+https://github.com/sdispater/cleo.git@master" == requires_dist
44 changes: 6 additions & 38 deletions tests/masonry/builders/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,48 +135,16 @@ def test_make_setup():
assert ns["extras_require"] == {"time": ["pendulum>=1.4,<2.0"]}


def test_make_pkg_info():
def test_make_pkg_info(mocker):
get_metadata_content = mocker.patch(
"poetry.masonry.builders.builder.Builder.get_metadata_content"
)
poetry = Poetry.create(project("complete"))

builder = SdistBuilder(poetry, NullEnv(), NullIO())
pkg_info = builder.build_pkg_info()
p = Parser()
parsed = p.parsestr(to_str(pkg_info))

assert parsed["Metadata-Version"] == "2.1"
assert parsed["Name"] == "my-package"
assert parsed["Version"] == "1.2.3"
assert parsed["Summary"] == "Some description."
assert parsed["Author"] == "Sébastien Eustace"
assert parsed["Author-email"] == "[email protected]"
assert parsed["Keywords"] == "packaging,dependency,poetry"
assert parsed["Requires-Python"] == ">=3.6,<4.0"

classifiers = parsed.get_all("Classifier")
assert classifiers == [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules",
]
builder.build_pkg_info()

extras = parsed.get_all("Provides-Extra")
assert extras == ["time"]

requires = parsed.get_all("Requires-Dist")
assert requires == [
"cachy[msgpack] (>=0.2.0,<0.3.0)",
"cleo (>=0.6,<0.7)",
'pendulum (>=1.4,<2.0); extra == "time"',
]

urls = parsed.get_all("Project-URL")
assert urls == [
"Documentation, https://poetry.eustace.io/docs",
"Repository, https://github.com/sdispater/poetry",
]
assert get_metadata_content.called


def test_make_pkg_info_any_python():
Expand Down
40 changes: 0 additions & 40 deletions tests/masonry/builders/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,43 +128,3 @@ def test_package_with_include(mocker):
assert "my_module.py" in names
assert "notes.txt" in names
assert "package_with_include/__init__.py" in names


def test_write_metadata_file_license_homepage_default(mocker):
# Preparation
mocked_poetry = mocker.Mock()
mocked_poetry.file.parent = Path(".")
mocked_poetry.package = ProjectPackage("pkg_name", "1.0.0")
mocked_file = mocker.Mock()
mocked_venv = mocker.Mock()
mocked_io = mocker.Mock()
# patch Module init inside Builder class
mocker.patch("poetry.masonry.builders.builder.Module")
w = WheelBuilder(mocked_poetry, mocked_venv, mocked_io)

# Action
w._write_metadata_file(mocked_file)

# Assertion
mocked_file.write.assert_any_call("Home-page: UNKNOWN\n")
mocked_file.write.assert_any_call("License: UNKNOWN\n")


def test_metadata_file_with_vcs_dependencies():
project_path = fixtures_dir / "with_vcs_dependency"
WheelBuilder.make(Poetry.create(str(project_path)), NullEnv(), NullIO())

whl = project_path / "dist" / "with_vcs_dependency-1.2.3-py3-none-any.whl"

assert whl.exists()

p = Parser()

with zipfile.ZipFile(str(whl)) as z:
metadata = p.parsestr(
to_str(z.read("with_vcs_dependency-1.2.3.dist-info/METADATA"))
)

requires_dist = metadata["Requires-Dist"]

assert "cleo @ git+https://github.com/sdispater/cleo.git@master" == requires_dist

0 comments on commit 17a5df2

Please sign in to comment.