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
|
||||
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)
|
||||
|
||||
Returns ``True`` if *filename* is a valid ZIP file based on its magic number,
|
||||
|
|
|
|||
|
|
@ -661,6 +661,14 @@ uuid
|
|||
in :rfc:`9562`.
|
||||
(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.
|
||||
|
||||
|
|
|
|||
|
|
@ -634,7 +634,7 @@ class TestPath(unittest.TestCase):
|
|||
"""
|
||||
data = io.BytesIO()
|
||||
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 = ''
|
||||
root = zipfile.Path(zf)
|
||||
(first,) = root.iterdir()
|
||||
|
|
@ -657,20 +657,3 @@ class DirtyZipInfo(zipfile.ZipInfo):
|
|||
def __init__(self, filename, *args, **kwargs):
|
||||
super().__init__(filename, *args, **kwargs)
|
||||
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 os
|
||||
import posixpath
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -2211,6 +2212,34 @@ class OtherTests(unittest.TestCase):
|
|||
zi = zipfile.ZipInfo(filename="empty")
|
||||
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):
|
||||
"""Ensure all required attributes are set."""
|
||||
zi = zipfile.ZipInfo()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import struct
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Self
|
||||
|
||||
try:
|
||||
import zlib # We may need its compression method
|
||||
|
|
@ -605,6 +606,24 @@ class ZipInfo:
|
|||
|
||||
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):
|
||||
"""Return True if this archive member is a directory."""
|
||||
if self.filename.endswith('/'):
|
||||
|
|
@ -1908,18 +1927,10 @@ class ZipFile:
|
|||
the name of the file in the archive."""
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
if not 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:
|
||||
if isinstance(zinfo_or_arcname, ZipInfo):
|
||||
zinfo = zinfo_or_arcname
|
||||
else:
|
||||
zinfo = ZipInfo(zinfo_or_arcname)._for_archive(self)
|
||||
|
||||
if not self.fp:
|
||||
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