summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/zipfile.rst9
-rw-r--r--Lib/test/test_zipfile/_path/test_path.py27
-rw-r--r--Lib/zipfile/_path/__init__.py7
-rw-r--r--Misc/NEWS.d/next/Library/2024-05-26-21-28-11.gh-issue-119588.wlLBK5.rst1
4 files changed, 32 insertions, 12 deletions
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index aad2028..a4d9a18 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -585,6 +585,15 @@ Path objects are traversable using the ``/`` operator or ``joinpath``.
Return ``True`` if the current context references a file.
+.. method:: Path.is_symlink()
+
+ Return ``True`` if the current context references a symbolic link.
+
+ .. versionadded:: 3.12
+
+ .. versionchanged:: 3.12.4
+ Prior to 3.12.4, ``is_symlink`` would unconditionally return ``False``.
+
.. method:: Path.exists()
Return ``True`` if the current context references a file or
diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py
index e5d2acf..99842ff 100644
--- a/Lib/test/test_zipfile/_path/test_path.py
+++ b/Lib/test/test_zipfile/_path/test_path.py
@@ -3,6 +3,7 @@ import itertools
import contextlib
import pathlib
import pickle
+import stat
import sys
import unittest
import zipfile
@@ -21,12 +22,17 @@ class jaraco:
Counter = Counter
+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
@@ -47,6 +53,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
@@ -61,6 +68,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
@@ -91,7 +101,7 @@ class TestPath(unittest.TestCase):
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()
@@ -111,7 +121,7 @@ class TestPath(unittest.TestCase):
@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()
@@ -126,7 +136,7 @@ class TestPath(unittest.TestCase):
@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")
@@ -230,7 +240,7 @@ class TestPath(unittest.TestCase):
@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"
@@ -296,7 +306,7 @@ class TestPath(unittest.TestCase):
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())
@@ -513,12 +523,9 @@ class TestPath(unittest.TestCase):
@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):
diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py
index 79ebb77..f5ea18c 100644
--- a/Lib/zipfile/_path/__init__.py
+++ b/Lib/zipfile/_path/__init__.py
@@ -5,6 +5,7 @@ import itertools
import contextlib
import pathlib
import re
+import stat
import sys
from .glob import Translator
@@ -390,9 +391,11 @@ class Path:
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:
diff --git a/Misc/NEWS.d/next/Library/2024-05-26-21-28-11.gh-issue-119588.wlLBK5.rst b/Misc/NEWS.d/next/Library/2024-05-26-21-28-11.gh-issue-119588.wlLBK5.rst
new file mode 100644
index 0000000..01321d8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-05-26-21-28-11.gh-issue-119588.wlLBK5.rst
@@ -0,0 +1 @@
+``zipfile.Path.is_symlink`` now assesses if the given path is a symlink.