From 8f09d1526b8ea2c9557d47ff5f895c04a65807f6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 26 May 2024 11:54:22 -0400 Subject: [PATCH 1/3] Expand the alpharep fixture to include a symlink. --- tests/test_path.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/test_path.py b/tests/test_path.py index 06233a3..fadfc59 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -3,6 +3,7 @@ import contextlib import pathlib import pickle +import stat import sys import unittest from .compat.overlay import zipfile @@ -15,12 +16,17 @@ from ._test_params import parameterize, Invoked +def _make_link(info: zipfile.ZipInfo): # type: ignore[name-defined] + info.external_attr |= stat.S_IFLNK << 16 + + def build_alpharep_fixture(): """ Create a zip file with this structure: . ├── a.txt + ├── n.txt (-> a.txt) ├── b │ ├── c.txt │ ├── d @@ -41,6 +47,7 @@ def build_alpharep_fixture(): - multiple files in a directory (b/c, b/f) - a directory containing only a directory (g/h) - a directory with files of different extensions (j/klm) + - a symlink (n) pointing to (a) "alpha" because it uses alphabet "rep" because it's a representative example @@ -55,6 +62,9 @@ def build_alpharep_fixture(): zf.writestr("j/k.bin", b"content of k") zf.writestr("j/l.baz", b"content of l") zf.writestr("j/m.bar", b"content of m") + zf.writestr("n.txt", b"a.txt") + _make_link(zf.infolist()[-1]) + zf.filename = "alpharep.zip" return zf @@ -85,7 +95,7 @@ def zipfile_ondisk(self, alpharep): def test_iterdir_and_types(self, alpharep): root = zipfile.Path(alpharep) assert root.is_dir() - a, b, g, j = root.iterdir() + a, k, b, g, j = root.iterdir() assert a.is_file() assert b.is_dir() assert g.is_dir() @@ -105,7 +115,7 @@ def test_is_file_missing(self, alpharep): @pass_alpharep def test_iterdir_on_file(self, alpharep): root = zipfile.Path(alpharep) - a, b, g, j = root.iterdir() + a, k, b, g, j = root.iterdir() with self.assertRaises(ValueError): a.iterdir() @@ -120,7 +130,7 @@ def test_subdir_is_dir(self, alpharep): @pass_alpharep def test_open(self, alpharep): root = zipfile.Path(alpharep) - a, b, g, j = root.iterdir() + a, k, b, g, j = root.iterdir() with a.open(encoding="utf-8") as strm: data = strm.read() self.assertEqual(data, "content of a") @@ -224,7 +234,7 @@ def test_open_missing_directory(self, alpharep): @pass_alpharep def test_read(self, alpharep): root = zipfile.Path(alpharep) - a, b, g, j = root.iterdir() + a, k, b, g, j = root.iterdir() assert a.read_text(encoding="utf-8") == "content of a" # Also check positional encoding arg (gh-101144). assert a.read_text("utf-8") == "content of a" @@ -290,7 +300,7 @@ def test_mutability(self, alpharep): reflect that change. """ root = zipfile.Path(alpharep) - a, b, g, j = root.iterdir() + a, k, b, g, j = root.iterdir() alpharep.writestr('foo.txt', 'foo') alpharep.writestr('bar/baz.txt', 'baz') assert any(child.name == 'foo.txt' for child in root.iterdir()) From 387dcea2ab13aedcc9f1c5a23ea5e83dfab441ea Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 26 May 2024 11:58:23 -0400 Subject: [PATCH 2/3] Revise test to capture new expectation. --- tests/test_path.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_path.py b/tests/test_path.py index fadfc59..418771c 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -515,14 +515,12 @@ def test_eq_hash(self, alpharep): root = zipfile.Path(alpharep) assert root in {root} + @__import__('pytest').mark.xfail(reason="Not implemented") @pass_alpharep def test_is_symlink(self, alpharep): - """ - See python/cpython#82102 for symlink support beyond this object. - """ - root = zipfile.Path(alpharep) - assert not root.is_symlink() + assert not root.joinpath('a.txt').is_symlink() + assert root.joinpath('n.txt').is_symlink() @pass_alpharep def test_relative_to(self, alpharep): From dc5fe8f4dd31e551f9bf76b5403e64f06f72a0c7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 26 May 2024 12:11:22 -0400 Subject: [PATCH 3/3] Implement is_symlink. Closes #117 --- newsfragments/117.feature.rst | 1 + tests/test_path.py | 1 - zipp/__init__.py | 7 +++++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 newsfragments/117.feature.rst diff --git a/newsfragments/117.feature.rst b/newsfragments/117.feature.rst new file mode 100644 index 0000000..239a902 --- /dev/null +++ b/newsfragments/117.feature.rst @@ -0,0 +1 @@ +Implement is_symlink. \ No newline at end of file diff --git a/tests/test_path.py b/tests/test_path.py index 418771c..bbf39b2 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -515,7 +515,6 @@ def test_eq_hash(self, alpharep): root = zipfile.Path(alpharep) assert root in {root} - @__import__('pytest').mark.xfail(reason="Not implemented") @pass_alpharep def test_is_symlink(self, alpharep): root = zipfile.Path(alpharep) diff --git a/zipp/__init__.py b/zipp/__init__.py index df3293a..b08016c 100644 --- a/zipp/__init__.py +++ b/zipp/__init__.py @@ -5,6 +5,7 @@ import contextlib import pathlib import re +import stat import sys from .compat.py310 import text_encoding @@ -391,9 +392,11 @@ def match(self, path_pattern): def is_symlink(self): """ - Return whether this path is a symlink. Always false (python/cpython#82102). + Return whether this path is a symlink. """ - return False + info = self.root.getinfo(self.at) + mode = info.external_attr >> 16 + return stat.S_ISLNK(mode) def glob(self, pattern): if not pattern: