Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions python-stdlib/zipfile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# zipfile

This library implements part of Python's `zipfile` module, namely allowing you
to open archives and read data out of them - as long as files are not compressed
with an unsupported compressor or encrypted.

One difference between CPython's `zipfile` implementation of `ZipFile` and this
module's is that the former does enumerate the files contained in an archive
when opening it, and this information is cached and still available even after
the archive has been closed via `ZipFile.close`. To save memory, this
implementation neither performs any automatic enumeration on open, nor caches
the files list. Attempting to interact with a `ZipFile` instance once it's been
closed will raise a `ValueError` exception.

## Installation

Use `mip` via `mpremote`:

```bash
> mpremote mip install zipfile
```

See [Package
management](https://docs.micropython.org/en/latest/reference/packages.html) for
more details on using `mip` and `mpremote`.
Binary file added python-stdlib/zipfile/deflate.zip
Binary file not shown.
2 changes: 2 additions & 0 deletions python-stdlib/zipfile/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
metadata(version="0.1.0")
package("zipfile")
Binary file added python-stdlib/zipfile/null.zip
Binary file not shown.
Binary file added python-stdlib/zipfile/stored.zip
Binary file not shown.
205 changes: 205 additions & 0 deletions python-stdlib/zipfile/test_zipfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import io
import sys
import unittest
import zipfile

# TODO: Find out which archiver can create ZSTD blobs
null_zip_contents = (
("null_bzip2.bin", 12, 4096, 46, (2026, 9, 4, 10, 1, 28), 0xC71C0011),
("null_deflate.bin", zipfile.ZIP_DEFLATED, 4096, 20, (2026, 9, 4, 10, 1, 32), 0xC71C0011),
("null_lzma.bin", 14, 4096, 41, (2026, 9, 4, 10, 1, 32), 0xC71C0011),
("null_stored.bin", zipfile.ZIP_STORED, 4096, 4096, (2026, 9, 4, 10, 1, 20), 0xC71C0011),
)

deflate_zip_contents = (
("directory/", True, 0, 0, 0, None),
("directory/hello_again.txt", False, 6656, 66, 0x946F8D8C, b"Hello again, MicroPython!\n"),
("empty", False, 0, 0, 0, None),
("hello.txt", False, 5120, 57, 0xA89FD9B3, b"Hello, MicroPython!\n"),
)

stored_zip_contents = (
("directory/", True, 0, 0, 0, None),
("directory/hello_again.txt", False, 6656, 6656, 0x946F8D8C, b"Hello again, MicroPython!\n"),
("empty", False, 0, 0, 0, None),
("hello.txt", False, 5120, 5120, 0xA89FD9B3, b"Hello, MicroPython!\n"),
)


class TestZipFile(unittest.TestCase):
def check_header(self, file, expected):
name, method, uncompressed_size, compressed_size, timestamp, crc32 = expected
self.assertEqual(name, file.filename)
self.assertEqual(method, file.compress_type)
self.assertEqual(uncompressed_size, file.file_size)
self.assertEqual(compressed_size, file.compress_size)
self.assertEqual(timestamp, file.date_time)
self.assertEqual(crc32, file.CRC)

def test_null_iter(self):
zf = zipfile.ZipFile("null.zip")
for index, info in enumerate(zf.infolist()):
self.check_header(info, null_zip_contents[index])
zf.close()

def test_null_missing(self):
with zipfile.ZipFile("null.zip") as zf:
with self.assertRaises(KeyError):
zf.getinfo("missing.txt")

def test_null_stored_read(self):
zf = zipfile.ZipFile("null.zip")
expected = bytes(4096)
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] != zipfile.ZIP_STORED:
continue
self.assertEqual(zf.read(name), expected)
zf.close()

def test_null_deflated_read(self):
if sys.implementation.name == "micropython":
try:
import deflate
except ImportError:
self.skipTest("no deflate module")
zf = zipfile.ZipFile("null.zip")
expected = bytes(4096)
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] != zipfile.ZIP_DEFLATED:
continue
self.assertEqual(zf.read(name), expected)
zf.close()

def test_null_unsupported_read(self):
if sys.implementation.name != "micropython":
self.skipTest("cannot guarantee lack of methods support")
zf = zipfile.ZipFile("null.zip")
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
with self.assertRaises(NotImplementedError):
zf.read(name)
zf.close()

def test_null_stored_open(self):
zf = zipfile.ZipFile("null.zip")
expected = bytes(4096)
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] != zipfile.ZIP_STORED:
continue
with zf.open(name) as f:
self.assertEqual(expected, f.read())
zf.close()

def test_null_deflated_open(self):
if sys.implementation.name == "micropython":
try:
import deflate
except ImportError:
self.skipTest("no deflate module")
zf = zipfile.ZipFile("null.zip")
expected = bytes(4096)
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] != zipfile.ZIP_DEFLATED:
continue
with zf.open(name) as f:
self.assertEqual(expected, f.read())
zf.close()

def test_null_unsupported_open(self):
if sys.implementation.name != "micropython":
self.skipTest("cannot guarantee lack of methods support")
zf = zipfile.ZipFile("null.zip")
for index, name in enumerate(zf.namelist()):
self.assertEqual(name, null_zip_contents[index][0])
if null_zip_contents[index][1] not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
with self.assertRaises(NotImplementedError):
with zf.open(name):
self.fail()
zf.close()

def test_null_getinfo(self):
zf = zipfile.ZipFile("null.zip")
for zc in null_zip_contents:
info = zf.getinfo(zc[0])
self.check_header(info, zc)
zf.close()

def test_context(self):
with zipfile.ZipFile("null.zip") as zf:
for index, info in enumerate(zf.infolist()):
self.check_header(info, null_zip_contents[index])

def test_close(self):
zf = zipfile.ZipFile("null.zip")
zf.close()
zf.close() # check this doesn't raise

# CPython doesn't raise as the archive directory is cached and not
# flushed once `close` is called. `namelist` and `infolist` do not
# raise on CPython.
if sys.implementation.name == "micropython":
with self.assertRaises(ValueError):
zf.namelist()
with self.assertRaises(ValueError):
zf.infolist()
with self.assertRaises(ValueError):
zf.open(null_zip_contents[0][0])
with self.assertRaises(ValueError):
with zf.open(null_zip_contents[0][0]):
self.fail()
with self.assertRaises(ValueError):
zf.read(null_zip_contents[0][0])

def check_contents(self, zf, found, expected, method):
self.assertEqual(found.is_dir(), expected[1])
self.assertEqual(found.file_size, expected[2])
self.assertEqual(found.compress_size, expected[3])
self.assertEqual(found.CRC, expected[4])
self.assertEqual(found.compress_type, zipfile.ZIP_STORED if expected[2] == 0 else method)
if payload := expected[5]:
data = payload * 256
with zf.open(expected[0]) as f:
self.assertEqual(f.read(), data)
self.assertEqual(zf.read(expected[0]), data)

def test_read_deflate(self):
if sys.implementation.name == "micropython":
try:
import deflate
except ImportError:
self.skipTest("no deflate module")
with zipfile.ZipFile("deflate.zip") as zf:
for file in deflate_zip_contents:
info = zf.getinfo(file[0])
self.check_contents(zf, info, file, zipfile.ZIP_DEFLATED)

def test_read_stored(self):
with zipfile.ZipFile("stored.zip") as zf:
for file in stored_zip_contents:
info = zf.getinfo(file[0])
self.check_contents(zf, info, file, zipfile.ZIP_STORED)

def test_is_zipfile(self):
for n in ("stored.zip", "null.zip", "deflate.zip"):
self.assertEqual(zipfile.is_zipfile(n), True)
with open(n, "rb") as f:
old_offset = f.tell()
self.assertEqual(zipfile.is_zipfile(f), True)
self.assertEqual(f.tell(), old_offset)

self.assertEqual(zipfile.is_zipfile("test_zipfile.py"), False)
with open("test_zipfile.py", "rb") as f:
old_offset = f.tell()
self.assertEqual(zipfile.is_zipfile(f), False)
self.assertEqual(f.tell(), old_offset)

self.assertEqual(zipfile.is_zipfile("missing.zip"), False)
self.assertEqual(zipfile.is_zipfile("zipfile"), False)

with io.BytesIO() as empty:
self.assertEqual(zipfile.is_zipfile(empty), False)
Loading
Loading