Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: prepare for EmailMessage #143

Merged
merged 4 commits into from
Sep 13, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ metadata = StandardMetadata.from_pyproject(parsed_pyproject, allow_extra_keys =
print(metadata.entrypoints) # same fields as defined in PEP 621

pkg_info = metadata.as_rfc822()
print(str(pkg_info)) # core metadata
print(bytes(pkg_info).decode("utf-8")) # core metadata
```


Expand Down
42 changes: 25 additions & 17 deletions pyproject_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import collections
import copy
import dataclasses
import email.utils
Expand Down Expand Up @@ -117,35 +116,44 @@ class ConfigurationWarning(UserWarning):
class RFC822Message:
"""Python-flavored RFC 822 message implementation."""

__slots__ = ('_headers', '_body')

def __init__(self) -> None:
self.headers: collections.OrderedDict[str, list[str]] = (
collections.OrderedDict()
)
self.body: str | None = None
self._headers: list[tuple[str, str]] = []
self._body: str | None = None

def items(self) -> list[tuple[str, str]]:
return self._headers.copy()

def get_all(self, name: str) -> None | list[str]:
return [v for k, v in self.items() if k == name]

def __setitem__(self, name: str, value: str | None) -> None:
if not value:
return
if name not in self.headers:
self.headers[name] = []
self.headers[name].append(value)
self._headers.append((name, value))

def __str__(self) -> str:
text = ''
for name, entries in self.headers.items():
for entry in entries:
lines = entry.strip('\n').split('\n')
text += f'{name}: {lines[0]}\n'
for line in lines[1:]:
text += ' ' * 8 + line + '\n'
for name, entry in self.items():
lines = entry.strip('\n').split('\n')
text += f'{name}: {lines[0]}\n'
for line in lines[1:]:
text += ' ' * 8 + line + '\n'
text += '\n'
if self.body:
text += self.body
if self._body:
text += self._body
return text

def __bytes__(self) -> bytes:
return str(self).encode()

def get_payload(self) -> str | None:
return self._body

def set_payload(self, body: str) -> None:
self._body = body


class DataFetcher:
def __init__(self, data: Mapping[str, Any]) -> None:
Expand Down Expand Up @@ -603,7 +611,7 @@ def write_to_rfc822(self, message: RFC822Message) -> None: # noqa: C901
if self.readme:
if self.readme.content_type:
message['Description-Content-Type'] = self.readme.content_type
message.body = self.readme.text
message.set_payload(self.readme.text)
# Core Metadata 2.2
if self.metadata_version != '2.1':
for field in self.dynamic:
Expand Down
10 changes: 6 additions & 4 deletions tests/test_rfc822.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
],
"""\
ItemA: ValueA1
ItemA: ValueA2
ItemB: ValueB
ItemC: ValueC
ItemA: ValueA2
""",
),
(
Expand Down Expand Up @@ -112,7 +112,8 @@ def test_body() -> None:
message['ItemB'] = 'ValueB'
message['ItemC'] = 'ValueC'

message.body = textwrap.dedent("""
message.set_payload(
textwrap.dedent("""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris congue semper
fermentum. Nunc vitae tempor ante. Aenean aliquet posuere lacus non faucibus.
In porttitor congue luctus. Vivamus eu dignissim orci. Donec egestas mi ac
Expand All @@ -126,6 +127,7 @@ def test_body() -> None:
finibus nulla. Donec sit amet ante in neque pulvinar faucibus sed nec justo.
Fusce hendrerit massa libero, sit amet pulvinar magna tempor quis.
""")
)

assert str(message) == textwrap.dedent("""\
ItemA: ValueA
Expand Down Expand Up @@ -164,7 +166,7 @@ def test_convert_optional_dependencies() -> None:
}
)
message = metadata.as_rfc822()
requires = message.headers['Requires-Dist']
requires = message.get_all('Requires-Dist')
assert requires == [
'foo; (os_name == "nt" or sys_platform == "win32") and extra == "test"',
'bar; os_name == "posix" and sys_platform == "linux" and extra == "test"',
Expand All @@ -191,6 +193,6 @@ def test_convert_author_email() -> None:
}
)
message = metadata.as_rfc822()
assert message.headers['Author-Email'] == [
assert message.get_all('Author-Email') == [
'"John Doe, Inc." <[email protected]>, "Kate Doe, LLC." <[email protected]>'
]
81 changes: 39 additions & 42 deletions tests/test_standard_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,42 +713,39 @@ def test_as_rfc822(monkeypatch: pytest.MonkeyPatch) -> None:
with open('pyproject.toml', 'rb') as f:
metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f))
core_metadata = metadata.as_rfc822()
assert core_metadata.headers == {
'Metadata-Version': ['2.1'],
'Name': ['full_metadata'],
'Summary': ['A package with all the metadata :)'],
'Version': ['3.2.1'],
'Keywords': ['trampolim,is,interesting'],
'Home-page': ['example.com'],
'Author': ['Example!'],
'Author-Email': ['Unknown <[email protected]>'],
'Maintainer-Email': ['Other Example <[email protected]>'],
'License': ['some license text'],
'Classifier': [
'Development Status :: 4 - Beta',
'Programming Language :: Python',
],
'Project-URL': [
'Homepage, example.com',
'Documentation, readthedocs.org',
'Repository, github.com/some/repo',
'Changelog, github.com/some/repo/blob/master/CHANGELOG.rst',
],
'Requires-Python': ['>=3.8'],
'Provides-Extra': ['test'],
'Requires-Dist': [
'dependency1',
'dependency2>1.0.0',
'dependency3[extra]',
'dependency4; os_name != "nt"',
'dependency5[other-extra]>1.0; os_name == "nt"',
'test_dependency; extra == "test"',
'test_dependency[test_extra]; extra == "test"',
assert core_metadata.items() == [
('Metadata-Version', '2.1'),
('Name', 'full_metadata'),
('Version', '3.2.1'),
('Summary', 'A package with all the metadata :)'),
('Keywords', 'trampolim,is,interesting'),
('Home-page', 'example.com'),
('Author', 'Example!'),
('Author-Email', 'Unknown <[email protected]>'),
('Maintainer-Email', 'Other Example <[email protected]>'),
('License', 'some license text'),
('Classifier', 'Development Status :: 4 - Beta'),
('Classifier', 'Programming Language :: Python'),
('Project-URL', 'Homepage, example.com'),
('Project-URL', 'Documentation, readthedocs.org'),
('Project-URL', 'Repository, github.com/some/repo'),
('Project-URL', 'Changelog, github.com/some/repo/blob/master/CHANGELOG.rst'),
('Requires-Python', '>=3.8'),
('Requires-Dist', 'dependency1'),
('Requires-Dist', 'dependency2>1.0.0'),
('Requires-Dist', 'dependency3[extra]'),
('Requires-Dist', 'dependency4; os_name != "nt"'),
('Requires-Dist', 'dependency5[other-extra]>1.0; os_name == "nt"'),
('Provides-Extra', 'test'),
('Requires-Dist', 'test_dependency; extra == "test"'),
('Requires-Dist', 'test_dependency[test_extra]; extra == "test"'),
(
'Requires-Dist',
'test_dependency[test_extra2]>3.0; os_name == "nt" and extra == "test"',
],
'Description-Content-Type': ['text/markdown'],
}
assert core_metadata.body == 'some readme 👋\n'
),
('Description-Content-Type', 'text/markdown'),
]
assert core_metadata.get_payload() == 'some readme 👋\n'


def test_as_rfc822_dynamic(monkeypatch: pytest.MonkeyPatch) -> None:
Expand All @@ -757,12 +754,12 @@ def test_as_rfc822_dynamic(monkeypatch: pytest.MonkeyPatch) -> None:
with open('pyproject.toml', 'rb') as f:
metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f))
core_metadata = metadata.as_rfc822()
assert dict(core_metadata.headers) == {
'Metadata-Version': ['2.2'],
'Name': ['dynamic-description'],
'Version': ['1.0.0'],
'Dynamic': ['description'],
}
assert core_metadata.items() == [
('Metadata-Version', '2.2'),
('Name', 'dynamic-description'),
('Version', '1.0.0'),
('Dynamic', 'description'),
]


@pytest.mark.parametrize('metadata_version', ['2.1', '2.2', '2.3'])
Expand All @@ -784,7 +781,7 @@ def test_as_rfc822_set_metadata(metadata_version: str) -> None:
)
assert metadata.metadata_version == metadata_version

rfc822 = str(metadata.as_rfc822())
rfc822 = bytes(metadata.as_rfc822()).decode('utf-8')

assert f'Metadata-Version: {metadata_version}' in rfc822

Expand Down