Skip to content

Commit

Permalink
Merge pull request #1 from python-packaging/sdist-without-requires
Browse files Browse the repository at this point in the history
Handle sdist files without requirements
  • Loading branch information
thatch authored Feb 27, 2024
2 parents 91b440c + 1940706 commit 56e4dba
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 16 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,9 @@ jobs:
- name: Install
run: |
python -m pip install --upgrade pip
make setup
pip install -U .
- name: Test
run: make test
- name: Lint
run: make lint
run: python -m metadata_please.tests

metadata_please:
runs-on: ${{ matrix.os }}
Expand Down
18 changes: 14 additions & 4 deletions metadata_please/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ def from_zip_sdist(zf: ZipFile) -> bytes:
"""
requires = [f for f in zf.namelist() if f.endswith("/requires.txt")]
requires.sort(key=len)
if not requires:
return b""

data = zf.read(requires[0])
assert data is not None
requires, extras = convert_sdist_requires(data.decode("utf-8"))
requires_lines, extras = convert_sdist_requires(data.decode("utf-8"))

buf: list[str] = []
for req in requires:
for req in requires_lines:
buf.append(f"Requires-Dist: {req}\n")
for extra in sorted(extras):
buf.append(f"Provides-Extra: {extra}\n")
Expand All @@ -27,6 +30,9 @@ def from_zip_sdist(zf: ZipFile) -> bytes:
def basic_metadata_from_zip_sdist(zf: ZipFile) -> BasicMetadata:
requires = [f for f in zf.namelist() if f.endswith("/requires.txt")]
requires.sort(key=len)
if not requires:
return BasicMetadata((), frozenset())

data = zf.read(requires[0])
assert data is not None
return BasicMetadata.from_sdist_pkg_info_and_requires(b"", data)
Expand All @@ -39,14 +45,16 @@ def from_tar_sdist(tf: TarFile) -> bytes:
# XXX Why do ZipFile and TarFile not have a common interface ?!
requires = [f for f in tf.getnames() if f.endswith("/requires.txt")]
requires.sort(key=len)
if not requires:
return b""

fo = tf.extractfile(requires[0])
assert fo is not None

requires, extras = convert_sdist_requires(fo.read().decode("utf-8"))
requires_lines, extras = convert_sdist_requires(fo.read().decode("utf-8"))

buf: list[str] = []
for req in requires:
for req in requires_lines:
buf.append(f"Requires-Dist: {req}\n")
for extra in sorted(extras):
buf.append(f"Provides-Extra: {extra}\n")
Expand All @@ -57,6 +65,8 @@ def basic_metadata_from_tar_sdist(tf: TarFile) -> BasicMetadata:
# XXX Why do ZipFile and TarFile not have a common interface ?!
requires = [f for f in tf.getnames() if f.endswith("/requires.txt")]
requires.sort(key=len)
if not requires:
return BasicMetadata((), frozenset())

fo = tf.extractfile(requires[0])
assert fo is not None
Expand Down
20 changes: 16 additions & 4 deletions metadata_please/tests/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,23 @@ def test_basic_metadata(self) -> None:
)
bm = basic_metadata_from_zip_sdist(z) # type: ignore
self.assertEqual(
["a", "b; extra == 'e'"],
("a", "b; extra == 'e'"),
bm.reqs,
)
self.assertEqual({"e"}, bm.provides_extra)

def test_basic_metadata_no_requires_file(self) -> None:
z = MemoryZipFile(
["foo.egg-info/PKG-INFO", "foo/__init__.py"],
read_value=b"\n",
)
bm = basic_metadata_from_zip_sdist(z) # type: ignore
self.assertEqual(
(),
bm.reqs,
)
self.assertEqual(set(), bm.provides_extra)

def test_basic_metadata_absl_py_09(self) -> None:
z = MemoryZipFile(
["foo.egg-info/requires.txt", "foo/__init__.py"],
Expand All @@ -60,12 +72,12 @@ def test_basic_metadata_absl_py_09(self) -> None:
)
bm = basic_metadata_from_zip_sdist(z) # type: ignore
self.assertEqual(
[
(
"six",
'enum34; python_version < "3.4"',
# Quoting on the following line is an implementation detail
"pytest; (python_version < \"3.4\") and extra == 'test'",
],
),
bm.reqs,
)
self.assertEqual({"test"}, bm.provides_extra)
Expand Down Expand Up @@ -102,7 +114,7 @@ def test_basic_metadata(self) -> None:
)
bm = basic_metadata_from_tar_sdist(t) # type: ignore
self.assertEqual(
["a", "b; extra == 'e'"],
("a", "b; extra == 'e'"),
bm.reqs,
)
self.assertEqual({"e"}, bm.provides_extra)
8 changes: 4 additions & 4 deletions metadata_please/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ class BasicMetadata:
# Popualted from Requires-Dist or requires.txt
reqs: Sequence[str]
# Populated from Provides-Extra
provides_extra: set[str]
provides_extra: frozenset[str]

@classmethod
def from_metadata(cls, metadata: bytes) -> BasicMetadata:
msg = message_from_string(metadata.decode("utf-8"))
return BasicMetadata(
msg.get_all("Requires-Dist") or (),
set(msg.get_all("Provides-Extra") or ()),
frozenset(msg.get_all("Provides-Extra") or ()),
)

@classmethod
Expand All @@ -33,7 +33,7 @@ def from_sdist_pkg_info_and_requires(
)


def convert_sdist_requires(data: str) -> tuple[list[str], set[str]]:
def convert_sdist_requires(data: str) -> tuple[tuple[str, ...], frozenset[str]]:
# This is reverse engineered from looking at a couple examples, but there
# does not appear to be a formal spec. Mentioned at
# https://setuptools.readthedocs.io/en/latest/formats.html#requires-txt
Expand Down Expand Up @@ -64,4 +64,4 @@ def convert_sdist_requires(data: str) -> tuple[list[str], set[str]]:
lst.append(f"{line}; {current_markers}")
else:
lst.append(line)
return lst, extras
return tuple(lst), frozenset(extras)

0 comments on commit 56e4dba

Please sign in to comment.