Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
8606e952
Commit
8606e952
authored
Mar 08, 2017
by
Serhiy Storchaka
Committed by
GitHub
Mar 08, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-28231: The zipfile module now accepts path-like objects for external paths. (#511)
parent
c351ce6a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
143 additions
and
21 deletions
+143
-21
Doc/library/zipfile.rst
Doc/library/zipfile.rst
+22
-2
Lib/test/test_zipfile.py
Lib/test/test_zipfile.py
+102
-15
Lib/zipfile.py
Lib/zipfile.py
+16
-4
Misc/NEWS
Misc/NEWS
+3
-0
No files found.
Doc/library/zipfile.rst
View file @
8606e952
...
@@ -132,8 +132,9 @@ ZipFile Objects
...
@@ -132,8 +132,9 @@ ZipFile Objects
.. class:: ZipFile(file, mode='
r
', compression=ZIP_STORED, allowZip64=True)
.. class:: ZipFile(file, mode='
r
', compression=ZIP_STORED, allowZip64=True)
Open a ZIP file, where *file* can be either a path to a file (a string) or a
Open a ZIP file, where *file* can be a path to a file (a string), a
file-like object. The *mode* parameter should be ``'
r
'`` to read an existing
file-like object or a :term:`path-like object`.
The *mode* parameter should be ``'
r
'`` to read an existing
file, ``'
w
'`` to truncate and write a new file, ``'
a
'`` to append to an
file, ``'
w
'`` to truncate and write a new file, ``'
a
'`` to append to an
existing file, or ``'
x
'`` to exclusively create and write a new file.
existing file, or ``'
x
'`` to exclusively create and write a new file.
If *mode* is ``'
x
'`` and *file* refers to an existing file,
If *mode* is ``'
x
'`` and *file* refers to an existing file,
...
@@ -183,6 +184,9 @@ ZipFile Objects
...
@@ -183,6 +184,9 @@ ZipFile Objects
Previously
,
a
plain
:
exc
:`
RuntimeError
`
was
raised
for
unrecognized
Previously
,
a
plain
:
exc
:`
RuntimeError
`
was
raised
for
unrecognized
compression
values
.
compression
values
.
..
versionchanged
::
3.6.2
The
*
file
*
parameter
accepts
a
:
term
:`
path
-
like
object
`.
..
method
::
ZipFile
.
close
()
..
method
::
ZipFile
.
close
()
...
@@ -284,6 +288,9 @@ ZipFile Objects
...
@@ -284,6 +288,9 @@ ZipFile Objects
Calling :meth:`extract` on a closed ZipFile will raise a
Calling :meth:`extract` on a closed ZipFile will raise a
:exc:`ValueError`. Previously, a :exc:`RuntimeError` was raised.
:exc:`ValueError`. Previously, a :exc:`RuntimeError` was raised.
.. versionchanged:: 3.6.2
The *path* parameter accepts a :term:`path-like object`.
.. method:: ZipFile.extractall(path=None, members=None, pwd=None)
.. method:: ZipFile.extractall(path=None, members=None, pwd=None)
...
@@ -304,6 +311,9 @@ ZipFile Objects
...
@@ -304,6 +311,9 @@ ZipFile Objects
Calling :meth:`extractall` on a closed ZipFile will raise a
Calling :meth:`extractall` on a closed ZipFile will raise a
:exc:`ValueError`. Previously, a :exc:`RuntimeError` was raised.
:exc:`ValueError`. Previously, a :exc:`RuntimeError` was raised.
.. versionchanged:: 3.6.2
The *path* parameter accepts a :term:`path-like object`.
.. method:: ZipFile.printdir()
.. method:: ZipFile.printdir()
...
@@ -403,6 +413,9 @@ ZipFile Objects
...
@@ -403,6 +413,9 @@ ZipFile Objects
The following data attributes are also available:
The following data attributes are also available:
.. attribute:: ZipFile.filename
Name of the ZIP file.
.. attribute:: ZipFile.debug
.. attribute:: ZipFile.debug
...
@@ -488,6 +501,9 @@ The :class:`PyZipFile` constructor takes the same parameters as the
...
@@ -488,6 +501,9 @@ The :class:`PyZipFile` constructor takes the same parameters as the
.. versionadded:: 3.4
.. versionadded:: 3.4
The *filterfunc* parameter.
The *filterfunc* parameter.
.. versionchanged:: 3.6.2
The *pathname* parameter accepts a :term:`path-like object`.
.. _zipinfo-objects:
.. _zipinfo-objects:
...
@@ -514,6 +530,10 @@ file:
...
@@ -514,6 +530,10 @@ file:
.. versionadded:: 3.6
.. versionadded:: 3.6
.. versionchanged:: 3.6.2
The *filename* parameter accepts a :term:`path-like object`.
Instances have the following methods and attributes:
Instances have the following methods and attributes:
.. method:: ZipInfo.is_dir()
.. method:: ZipInfo.is_dir()
...
...
Lib/test/test_zipfile.py
View file @
8606e952
...
@@ -2,6 +2,7 @@ import contextlib
...
@@ -2,6 +2,7 @@ import contextlib
import
io
import
io
import
os
import
os
import
importlib.util
import
importlib.util
import
pathlib
import
posixpath
import
posixpath
import
time
import
time
import
struct
import
struct
...
@@ -13,7 +14,7 @@ from tempfile import TemporaryFile
...
@@ -13,7 +14,7 @@ from tempfile import TemporaryFile
from
random
import
randint
,
random
,
getrandbits
from
random
import
randint
,
random
,
getrandbits
from
test.support
import
script_helper
from
test.support
import
script_helper
from
test.support
import
(
TESTFN
,
findfile
,
unlink
,
rmtree
,
temp_dir
,
from
test.support
import
(
TESTFN
,
findfile
,
unlink
,
rmtree
,
temp_dir
,
temp_cwd
,
requires_zlib
,
requires_bz2
,
requires_lzma
,
requires_zlib
,
requires_bz2
,
requires_lzma
,
captured_stdout
,
check_warnings
)
captured_stdout
,
check_warnings
)
...
@@ -148,6 +149,12 @@ class AbstractTestsWithSourceFile:
...
@@ -148,6 +149,12 @@ class AbstractTestsWithSourceFile:
for
f
in
get_files
(
self
):
for
f
in
get_files
(
self
):
self
.
zip_open_test
(
f
,
self
.
compression
)
self
.
zip_open_test
(
f
,
self
.
compression
)
def
test_open_with_pathlike
(
self
):
path
=
pathlib
.
Path
(
TESTFN2
)
self
.
zip_open_test
(
path
,
self
.
compression
)
with
zipfile
.
ZipFile
(
path
,
"r"
,
self
.
compression
)
as
zipfp
:
self
.
assertIsInstance
(
zipfp
.
filename
,
str
)
def
zip_random_open_test
(
self
,
f
,
compression
):
def
zip_random_open_test
(
self
,
f
,
compression
):
self
.
make_test_archive
(
f
,
compression
)
self
.
make_test_archive
(
f
,
compression
)
...
@@ -906,22 +913,56 @@ class PyZipFileTests(unittest.TestCase):
...
@@ -906,22 +913,56 @@ class PyZipFileTests(unittest.TestCase):
finally
:
finally
:
rmtree
(
TESTFN2
)
rmtree
(
TESTFN2
)
def
test_write_pathlike
(
self
):
os
.
mkdir
(
TESTFN2
)
try
:
with
open
(
os
.
path
.
join
(
TESTFN2
,
"mod1.py"
),
"w"
)
as
fp
:
fp
.
write
(
"print(42)
\
n
"
)
with
TemporaryFile
()
as
t
,
zipfile
.
PyZipFile
(
t
,
"w"
)
as
zipfp
:
zipfp
.
writepy
(
pathlib
.
Path
(
TESTFN2
)
/
"mod1.py"
)
names
=
zipfp
.
namelist
()
self
.
assertCompiledIn
(
'mod1.py'
,
names
)
finally
:
rmtree
(
TESTFN2
)
class
ExtractTests
(
unittest
.
TestCase
):
class
ExtractTests
(
unittest
.
TestCase
):
def
test_extract
(
self
):
def
make_test_file
(
self
):
with
zipfile
.
ZipFile
(
TESTFN2
,
"w"
,
zipfile
.
ZIP_STORED
)
as
zipfp
:
with
zipfile
.
ZipFile
(
TESTFN2
,
"w"
,
zipfile
.
ZIP_STORED
)
as
zipfp
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
zipfp
.
writestr
(
fpath
,
fdata
)
zipfp
.
writestr
(
fpath
,
fdata
)
def
test_extract
(
self
):
with
temp_cwd
():
self
.
make_test_file
()
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
writtenfile
=
zipfp
.
extract
(
fpath
)
# make sure it was written to the right place
correctfile
=
os
.
path
.
join
(
os
.
getcwd
(),
fpath
)
correctfile
=
os
.
path
.
normpath
(
correctfile
)
self
.
assertEqual
(
writtenfile
,
correctfile
)
# make sure correct data is in correct file
with
open
(
writtenfile
,
"rb"
)
as
f
:
self
.
assertEqual
(
fdata
.
encode
(),
f
.
read
())
unlink
(
writtenfile
)
def
_test_extract_with_target
(
self
,
target
):
self
.
make_test_file
()
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
writtenfile
=
zipfp
.
extract
(
fpath
)
writtenfile
=
zipfp
.
extract
(
fpath
,
target
)
# make sure it was written to the right place
# make sure it was written to the right place
correctfile
=
os
.
path
.
join
(
os
.
getcwd
()
,
fpath
)
correctfile
=
os
.
path
.
join
(
target
,
fpath
)
correctfile
=
os
.
path
.
normpath
(
correctfile
)
correctfile
=
os
.
path
.
normpath
(
correctfile
)
self
.
assertTrue
(
os
.
path
.
samefile
(
writtenfile
,
correctfile
),
(
writtenfile
,
target
))
self
.
assertEqual
(
writtenfile
,
correctfile
)
# make sure correct data is in correct file
# make sure correct data is in correct file
with
open
(
writtenfile
,
"rb"
)
as
f
:
with
open
(
writtenfile
,
"rb"
)
as
f
:
...
@@ -929,26 +970,50 @@ class ExtractTests(unittest.TestCase):
...
@@ -929,26 +970,50 @@ class ExtractTests(unittest.TestCase):
unlink
(
writtenfile
)
unlink
(
writtenfile
)
# remove the test file subdirectories
unlink
(
TESTFN2
)
rmtree
(
os
.
path
.
join
(
os
.
getcwd
(),
'ziptest2dir'
))
def
test_extract_with_target
(
self
):
with
temp_dir
()
as
extdir
:
self
.
_test_extract_with_target
(
extdir
)
def
test_extract_with_target_pathlike
(
self
):
with
temp_dir
()
as
extdir
:
self
.
_test_extract_with_target
(
pathlib
.
Path
(
extdir
))
def
test_extract_all
(
self
):
def
test_extract_all
(
self
):
with
zipfile
.
ZipFile
(
TESTFN2
,
"w"
,
zipfile
.
ZIP_STORED
)
as
zipfp
:
with
temp_cwd
():
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
self
.
make_test_file
()
zipfp
.
writestr
(
fpath
,
fdata
)
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
zipfp
.
extractall
()
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
outfile
=
os
.
path
.
join
(
os
.
getcwd
(),
fpath
)
with
open
(
outfile
,
"rb"
)
as
f
:
self
.
assertEqual
(
fdata
.
encode
(),
f
.
read
())
unlink
(
outfile
)
def
_test_extract_all_with_target
(
self
,
target
):
self
.
make_test_file
()
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
with
zipfile
.
ZipFile
(
TESTFN2
,
"r"
)
as
zipfp
:
zipfp
.
extractall
()
zipfp
.
extractall
(
target
)
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
for
fpath
,
fdata
in
SMALL_TEST_DATA
:
outfile
=
os
.
path
.
join
(
os
.
getcwd
()
,
fpath
)
outfile
=
os
.
path
.
join
(
target
,
fpath
)
with
open
(
outfile
,
"rb"
)
as
f
:
with
open
(
outfile
,
"rb"
)
as
f
:
self
.
assertEqual
(
fdata
.
encode
(),
f
.
read
())
self
.
assertEqual
(
fdata
.
encode
(),
f
.
read
())
unlink
(
outfile
)
unlink
(
outfile
)
# remove the test file subdirectories
unlink
(
TESTFN2
)
rmtree
(
os
.
path
.
join
(
os
.
getcwd
(),
'ziptest2dir'
))
def
test_extract_all_with_target
(
self
):
with
temp_dir
()
as
extdir
:
self
.
_test_extract_all_with_target
(
extdir
)
def
test_extract_all_with_target_pathlike
(
self
):
with
temp_dir
()
as
extdir
:
self
.
_test_extract_all_with_target
(
pathlib
.
Path
(
extdir
))
def
check_file
(
self
,
filename
,
content
):
def
check_file
(
self
,
filename
,
content
):
self
.
assertTrue
(
os
.
path
.
isfile
(
filename
))
self
.
assertTrue
(
os
.
path
.
isfile
(
filename
))
...
@@ -1188,6 +1253,8 @@ class OtherTests(unittest.TestCase):
...
@@ -1188,6 +1253,8 @@ class OtherTests(unittest.TestCase):
with
open
(
TESTFN
,
"w"
)
as
fp
:
with
open
(
TESTFN
,
"w"
)
as
fp
:
fp
.
write
(
"this is not a legal zip file
\
n
"
)
fp
.
write
(
"this is not a legal zip file
\
n
"
)
self
.
assertFalse
(
zipfile
.
is_zipfile
(
TESTFN
))
self
.
assertFalse
(
zipfile
.
is_zipfile
(
TESTFN
))
# - passing a path-like object
self
.
assertFalse
(
zipfile
.
is_zipfile
(
pathlib
.
Path
(
TESTFN
)))
# - passing a file object
# - passing a file object
with
open
(
TESTFN
,
"rb"
)
as
fp
:
with
open
(
TESTFN
,
"rb"
)
as
fp
:
self
.
assertFalse
(
zipfile
.
is_zipfile
(
fp
))
self
.
assertFalse
(
zipfile
.
is_zipfile
(
fp
))
...
@@ -2033,6 +2100,26 @@ class ZipInfoTests(unittest.TestCase):
...
@@ -2033,6 +2100,26 @@ class ZipInfoTests(unittest.TestCase):
zi
=
zipfile
.
ZipInfo
.
from_file
(
__file__
)
zi
=
zipfile
.
ZipInfo
.
from_file
(
__file__
)
self
.
assertEqual
(
posixpath
.
basename
(
zi
.
filename
),
'test_zipfile.py'
)
self
.
assertEqual
(
posixpath
.
basename
(
zi
.
filename
),
'test_zipfile.py'
)
self
.
assertFalse
(
zi
.
is_dir
())
self
.
assertFalse
(
zi
.
is_dir
())
self
.
assertEqual
(
zi
.
file_size
,
os
.
path
.
getsize
(
__file__
))
def
test_from_file_pathlike
(
self
):
zi
=
zipfile
.
ZipInfo
.
from_file
(
pathlib
.
Path
(
__file__
))
self
.
assertEqual
(
posixpath
.
basename
(
zi
.
filename
),
'test_zipfile.py'
)
self
.
assertFalse
(
zi
.
is_dir
())
self
.
assertEqual
(
zi
.
file_size
,
os
.
path
.
getsize
(
__file__
))
def
test_from_file_bytes
(
self
):
zi
=
zipfile
.
ZipInfo
.
from_file
(
os
.
fsencode
(
__file__
),
'test'
)
self
.
assertEqual
(
posixpath
.
basename
(
zi
.
filename
),
'test'
)
self
.
assertFalse
(
zi
.
is_dir
())
self
.
assertEqual
(
zi
.
file_size
,
os
.
path
.
getsize
(
__file__
))
def
test_from_file_fileno
(
self
):
with
open
(
__file__
,
'rb'
)
as
f
:
zi
=
zipfile
.
ZipInfo
.
from_file
(
f
.
fileno
(),
'test'
)
self
.
assertEqual
(
posixpath
.
basename
(
zi
.
filename
),
'test'
)
self
.
assertFalse
(
zi
.
is_dir
())
self
.
assertEqual
(
zi
.
file_size
,
os
.
path
.
getsize
(
__file__
))
def
test_from_dir
(
self
):
def
test_from_dir
(
self
):
dirpath
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
dirpath
=
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
...
...
Lib/zipfile.py
View file @
8606e952
...
@@ -478,6 +478,8 @@ class ZipInfo (object):
...
@@ -478,6 +478,8 @@ class ZipInfo (object):
this will be the same as filename, but without a drive letter and with
this will be the same as filename, but without a drive letter and with
leading path separators removed).
leading path separators removed).
"""
"""
if
isinstance
(
filename
,
os
.
PathLike
):
filename
=
os
.
fspath
(
filename
)
st
=
os
.
stat
(
filename
)
st
=
os
.
stat
(
filename
)
isdir
=
stat
.
S_ISDIR
(
st
.
st_mode
)
isdir
=
stat
.
S_ISDIR
(
st
.
st_mode
)
mtime
=
time
.
localtime
(
st
.
st_mtime
)
mtime
=
time
.
localtime
(
st
.
st_mtime
)
...
@@ -1069,6 +1071,8 @@ class ZipFile:
...
@@ -1069,6 +1071,8 @@ class ZipFile:
self
.
_comment
=
b''
self
.
_comment
=
b''
# Check if we were passed a file-like object
# Check if we were passed a file-like object
if
isinstance
(
file
,
os
.
PathLike
):
file
=
os
.
fspath
(
file
)
if
isinstance
(
file
,
str
):
if
isinstance
(
file
,
str
):
# No, it's a filename
# No, it's a filename
self
.
_filePassed
=
0
self
.
_filePassed
=
0
...
@@ -1469,11 +1473,10 @@ class ZipFile:
...
@@ -1469,11 +1473,10 @@ class ZipFile:
as possible. `member' may be a filename or a ZipInfo object. You can
as possible. `member' may be a filename or a ZipInfo object. You can
specify a different directory using `path'.
specify a different directory using `path'.
"""
"""
if
not
isinstance
(
member
,
ZipInfo
):
member
=
self
.
getinfo
(
member
)
if
path
is
None
:
if
path
is
None
:
path
=
os
.
getcwd
()
path
=
os
.
getcwd
()
else
:
path
=
os
.
fspath
(
path
)
return
self
.
_extract_member
(
member
,
path
,
pwd
)
return
self
.
_extract_member
(
member
,
path
,
pwd
)
...
@@ -1486,8 +1489,13 @@ class ZipFile:
...
@@ -1486,8 +1489,13 @@ class ZipFile:
if
members
is
None
:
if
members
is
None
:
members
=
self
.
namelist
()
members
=
self
.
namelist
()
if
path
is
None
:
path
=
os
.
getcwd
()
else
:
path
=
os
.
fspath
(
path
)
for
zipinfo
in
members
:
for
zipinfo
in
members
:
self
.
extract
(
zipinfo
,
path
,
pwd
)
self
.
_extract_member
(
zipinfo
,
path
,
pwd
)
@
classmethod
@
classmethod
def
_sanitize_windows_name
(
cls
,
arcname
,
pathsep
):
def
_sanitize_windows_name
(
cls
,
arcname
,
pathsep
):
...
@@ -1508,6 +1516,9 @@ class ZipFile:
...
@@ -1508,6 +1516,9 @@ class ZipFile:
"""Extract the ZipInfo object 'member' to a physical
"""Extract the ZipInfo object 'member' to a physical
file on the path targetpath.
file on the path targetpath.
"""
"""
if
not
isinstance
(
member
,
ZipInfo
):
member
=
self
.
getinfo
(
member
)
# build the destination pathname, replacing
# build the destination pathname, replacing
# forward slashes to platform specific separators.
# forward slashes to platform specific separators.
arcname
=
member
.
filename
.
replace
(
'/'
,
os
.
path
.
sep
)
arcname
=
member
.
filename
.
replace
(
'/'
,
os
.
path
.
sep
)
...
@@ -1800,6 +1811,7 @@ class PyZipFile(ZipFile):
...
@@ -1800,6 +1811,7 @@ class PyZipFile(ZipFile):
If filterfunc(pathname) is given, it is called with every argument.
If filterfunc(pathname) is given, it is called with every argument.
When it is False, the file or directory is skipped.
When it is False, the file or directory is skipped.
"""
"""
pathname
=
os
.
fspath
(
pathname
)
if
filterfunc
and
not
filterfunc
(
pathname
):
if
filterfunc
and
not
filterfunc
(
pathname
):
if
self
.
debug
:
if
self
.
debug
:
label
=
'path'
if
os
.
path
.
isdir
(
pathname
)
else
'file'
label
=
'path'
if
os
.
path
.
isdir
(
pathname
)
else
'file'
...
...
Misc/NEWS
View file @
8606e952
...
@@ -270,6 +270,9 @@ Extension Modules
...
@@ -270,6 +270,9 @@ Extension Modules
Library
Library
-------
-------
- bpo-28231: The zipfile module now accepts path-like objects for external
paths.
- bpo-26915: index() and count() methods of collections.abc.Sequence now
- bpo-26915: index() and count() methods of collections.abc.Sequence now
check identity before checking equality when do comparisons.
check identity before checking equality when do comparisons.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment