mirror of
https://github.com/python/cpython.git
synced 2025-10-22 14:42:22 +00:00
gh-123424: add ZipInfo._for_archive
to set suitable default properties (#123429)
--------- Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
parent
ffece5590e
commit
7e819ce0f3
6 changed files with 72 additions and 29 deletions
|
@ -84,6 +84,17 @@ The module defines the following items:
|
||||||
formerly protected :attr:`!_compresslevel`. The older protected name
|
formerly protected :attr:`!_compresslevel`. The older protected name
|
||||||
continues to work as a property for backwards compatibility.
|
continues to work as a property for backwards compatibility.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: _for_archive(archive)
|
||||||
|
|
||||||
|
Resolve the date_time, compression attributes, and external attributes
|
||||||
|
to suitable defaults as used by :meth:`ZipFile.writestr`.
|
||||||
|
|
||||||
|
Returns self for chaining.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
|
|
||||||
.. function:: is_zipfile(filename)
|
.. function:: is_zipfile(filename)
|
||||||
|
|
||||||
Returns ``True`` if *filename* is a valid ZIP file based on its magic number,
|
Returns ``True`` if *filename* is a valid ZIP file based on its magic number,
|
||||||
|
|
|
@ -661,6 +661,14 @@ uuid
|
||||||
in :rfc:`9562`.
|
in :rfc:`9562`.
|
||||||
(Contributed by Bénédikt Tran in :gh:`89083`.)
|
(Contributed by Bénédikt Tran in :gh:`89083`.)
|
||||||
|
|
||||||
|
zipinfo
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Added :func:`ZipInfo._for_archive <zipfile.ZipInfo._for_archive>`
|
||||||
|
to resolve suitable defaults for a :class:`~zipfile.ZipInfo` object
|
||||||
|
as used by :func:`ZipFile.writestr <zipfile.ZipFile.writestr>`.
|
||||||
|
|
||||||
|
(Contributed by Bénédikt Tran in :gh:`123424`.)
|
||||||
|
|
||||||
.. Add improved modules above alphabetically, not here at the end.
|
.. Add improved modules above alphabetically, not here at the end.
|
||||||
|
|
||||||
|
|
|
@ -634,7 +634,7 @@ class TestPath(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
data = io.BytesIO()
|
data = io.BytesIO()
|
||||||
zf = zipfile.ZipFile(data, "w")
|
zf = zipfile.ZipFile(data, "w")
|
||||||
zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
|
zf.writestr(DirtyZipInfo("foo\\bar")._for_archive(zf), b"content")
|
||||||
zf.filename = ''
|
zf.filename = ''
|
||||||
root = zipfile.Path(zf)
|
root = zipfile.Path(zf)
|
||||||
(first,) = root.iterdir()
|
(first,) = root.iterdir()
|
||||||
|
@ -657,20 +657,3 @@ class DirtyZipInfo(zipfile.ZipInfo):
|
||||||
def __init__(self, filename, *args, **kwargs):
|
def __init__(self, filename, *args, **kwargs):
|
||||||
super().__init__(filename, *args, **kwargs)
|
super().__init__(filename, *args, **kwargs)
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def for_name(cls, name, archive):
|
|
||||||
"""
|
|
||||||
Construct the same way that ZipFile.writestr does.
|
|
||||||
|
|
||||||
TODO: extract this functionality and re-use
|
|
||||||
"""
|
|
||||||
self = cls(filename=name, date_time=time.localtime(time.time())[:6])
|
|
||||||
self.compress_type = archive.compression
|
|
||||||
self.compress_level = archive.compresslevel
|
|
||||||
if self.filename.endswith('/'): # pragma: no cover
|
|
||||||
self.external_attr = 0o40775 << 16 # drwxrwxr-x
|
|
||||||
self.external_attr |= 0x10 # MS-DOS directory flag
|
|
||||||
else:
|
|
||||||
self.external_attr = 0o600 << 16 # ?rw-------
|
|
||||||
return self
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
|
import stat
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -2211,6 +2212,34 @@ class OtherTests(unittest.TestCase):
|
||||||
zi = zipfile.ZipInfo(filename="empty")
|
zi = zipfile.ZipInfo(filename="empty")
|
||||||
self.assertEqual(repr(zi), "<ZipInfo filename='empty' file_size=0>")
|
self.assertEqual(repr(zi), "<ZipInfo filename='empty' file_size=0>")
|
||||||
|
|
||||||
|
def test_for_archive(self):
|
||||||
|
base_filename = TESTFN2.rstrip('/')
|
||||||
|
|
||||||
|
with zipfile.ZipFile(TESTFN, mode="w", compresslevel=1,
|
||||||
|
compression=zipfile.ZIP_STORED) as zf:
|
||||||
|
# no trailing forward slash
|
||||||
|
zi = zipfile.ZipInfo(base_filename)._for_archive(zf)
|
||||||
|
self.assertEqual(zi.compress_level, 1)
|
||||||
|
self.assertEqual(zi.compress_type, zipfile.ZIP_STORED)
|
||||||
|
# ?rw- --- ---
|
||||||
|
filemode = stat.S_IRUSR | stat.S_IWUSR
|
||||||
|
# filemode is stored as the highest 16 bits of external_attr
|
||||||
|
self.assertEqual(zi.external_attr >> 16, filemode)
|
||||||
|
self.assertEqual(zi.external_attr & 0xFF, 0) # no MS-DOS flag
|
||||||
|
|
||||||
|
with zipfile.ZipFile(TESTFN, mode="w", compresslevel=1,
|
||||||
|
compression=zipfile.ZIP_STORED) as zf:
|
||||||
|
# with a trailing slash
|
||||||
|
zi = zipfile.ZipInfo(f'{base_filename}/')._for_archive(zf)
|
||||||
|
self.assertEqual(zi.compress_level, 1)
|
||||||
|
self.assertEqual(zi.compress_type, zipfile.ZIP_STORED)
|
||||||
|
# d rwx rwx r-x
|
||||||
|
filemode = stat.S_IFDIR
|
||||||
|
filemode |= stat.S_IRWXU | stat.S_IRWXG
|
||||||
|
filemode |= stat.S_IROTH | stat.S_IXOTH
|
||||||
|
self.assertEqual(zi.external_attr >> 16, filemode)
|
||||||
|
self.assertEqual(zi.external_attr & 0xFF, 0x10) # MS-DOS flag
|
||||||
|
|
||||||
def test_create_empty_zipinfo_default_attributes(self):
|
def test_create_empty_zipinfo_default_attributes(self):
|
||||||
"""Ensure all required attributes are set."""
|
"""Ensure all required attributes are set."""
|
||||||
zi = zipfile.ZipInfo()
|
zi = zipfile.ZipInfo()
|
||||||
|
|
|
@ -13,6 +13,7 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zlib # We may need its compression method
|
import zlib # We may need its compression method
|
||||||
|
@ -605,6 +606,24 @@ class ZipInfo:
|
||||||
|
|
||||||
return zinfo
|
return zinfo
|
||||||
|
|
||||||
|
def _for_archive(self, archive: ZipFile) -> Self:
|
||||||
|
"""Resolve suitable defaults from the archive.
|
||||||
|
|
||||||
|
Resolve the date_time, compression attributes, and external attributes
|
||||||
|
to suitable defaults as used by :method:`ZipFile.writestr`.
|
||||||
|
|
||||||
|
Return self.
|
||||||
|
"""
|
||||||
|
self.date_time = time.localtime(time.time())[:6]
|
||||||
|
self.compress_type = archive.compression
|
||||||
|
self.compress_level = archive.compresslevel
|
||||||
|
if self.filename.endswith('/'): # pragma: no cover
|
||||||
|
self.external_attr = 0o40775 << 16 # drwxrwxr-x
|
||||||
|
self.external_attr |= 0x10 # MS-DOS directory flag
|
||||||
|
else:
|
||||||
|
self.external_attr = 0o600 << 16 # ?rw-------
|
||||||
|
return self
|
||||||
|
|
||||||
def is_dir(self):
|
def is_dir(self):
|
||||||
"""Return True if this archive member is a directory."""
|
"""Return True if this archive member is a directory."""
|
||||||
if self.filename.endswith('/'):
|
if self.filename.endswith('/'):
|
||||||
|
@ -1908,18 +1927,10 @@ class ZipFile:
|
||||||
the name of the file in the archive."""
|
the name of the file in the archive."""
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.encode("utf-8")
|
data = data.encode("utf-8")
|
||||||
if not isinstance(zinfo_or_arcname, ZipInfo):
|
if isinstance(zinfo_or_arcname, ZipInfo):
|
||||||
zinfo = ZipInfo(filename=zinfo_or_arcname,
|
|
||||||
date_time=time.localtime(time.time())[:6])
|
|
||||||
zinfo.compress_type = self.compression
|
|
||||||
zinfo.compress_level = self.compresslevel
|
|
||||||
if zinfo.filename.endswith('/'):
|
|
||||||
zinfo.external_attr = 0o40775 << 16 # drwxrwxr-x
|
|
||||||
zinfo.external_attr |= 0x10 # MS-DOS directory flag
|
|
||||||
else:
|
|
||||||
zinfo.external_attr = 0o600 << 16 # ?rw-------
|
|
||||||
else:
|
|
||||||
zinfo = zinfo_or_arcname
|
zinfo = zinfo_or_arcname
|
||||||
|
else:
|
||||||
|
zinfo = ZipInfo(zinfo_or_arcname)._for_archive(self)
|
||||||
|
|
||||||
if not self.fp:
|
if not self.fp:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add :meth:`zipfile.ZipInfo._for_archive` setting default properties on :class:`~zipfile.ZipInfo` objects. Patch by Bénédikt Tran and Jason R. Coombs.
|
Loading…
Add table
Add a link
Reference in a new issue