Skip to content
This repository was archived by the owner on Aug 25, 2024. It is now read-only.

Commit fd0ae31

Browse files
sudharsana-kjlJohn Andersen
authored and
John Andersen
committed
source: file: Add support for .zip file
1 parent bf3493e commit fd0ae31

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Added support for lzma file source
2525
- Added support for xz file source
2626
- Added Data Flow Facilitator
27+
- Added support for zip file source
2728
### Changed
2829
- Restructured documentation to docs folder and moved from rST to markdown
2930
- Git feature cloc logs if no binaries are in path

dffml/source/file.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
import gzip
77
import bz2
88
import lzma
9+
import zipfile
10+
import io
911
from .source import Source
1012
from .log import LOGGER
13+
from contextlib import contextmanager
1114

1215
LOGGER = LOGGER.getChild('file')
1316

@@ -49,6 +52,8 @@ async def _open(self):
4952
elif self.filename[::-1].startswith(('.xz')[::-1]) or \
5053
self.filename[::-1].startswith(('.lzma')[::-1]):
5154
opener = lzma.open(self.filename, 'rt')
55+
elif self.filename[::-1].startswith(('.zip')[::-1]):
56+
opener = self.zip_opener_helper()
5257
else:
5358
opener = open(self.filename, 'r')
5459
with opener as fd:
@@ -66,11 +71,28 @@ async def _close(self):
6671
elif self.filename[::-1].startswith(('.xz')[::-1]) or \
6772
self.filename[::-1].startswith(('.lzma')[::-1]):
6873
close = lzma.open(self.filename, 'wt')
74+
elif self.filename[::-1].startswith(('.zip')[::-1]):
75+
close = self.zip_closer_helper()
6976
else:
7077
close = open(self.filename, 'w')
7178
with close as fd:
7279
await self.dump_fd(fd)
7380

81+
@contextmanager
82+
def zip_opener_helper(self):
83+
with zipfile.ZipFile(self.filename) as archive:
84+
with archive.open(self.__class__.__qualname__, mode='r') as fd:
85+
yield fd
86+
87+
@contextmanager
88+
def zip_closer_helper(self):
89+
with zipfile.ZipFile(self.filename, 'w',
90+
compression=zipfile.ZIP_BZIP2) as archive:
91+
with io.TextIOWrapper(archive.open(self.__class__.__qualname__,
92+
mode='w',
93+
force_zip64=True)) as fd:
94+
yield fd
95+
7496
@abc.abstractmethod
7597
async def load_fd(self, fd):
7698
pass # pragma: no cover

tests/source/test_file.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import tempfile
1212
import unittest
1313
import collections
14-
from unittest.mock import patch, mock_open
14+
from unittest.mock import patch, mock_open, Mock
1515
from functools import wraps
1616
from contextlib import contextmanager
1717
from typing import List, Dict, Any, Optional, Tuple, AsyncIterator
@@ -32,10 +32,35 @@ async def repo(self, src_url: str):
3232
pass # pragma: no cover
3333

3434
async def load_fd(self, fd):
35-
pass # pragma: no cover
35+
self.loaded_fd = fd
3636

3737
async def dump_fd(self, fd):
38-
pass # pragma: no cover
38+
self.dumped_fd = fd
39+
40+
class MockZipFile(object):
41+
42+
def __init__(self, *_args, **_kwargs):
43+
self.files = [Mock(filename='foo'), Mock(filename='bar')]
44+
45+
def __enter__(self):
46+
return self
47+
48+
def __exit__(self, exc_type, exc_val, exc_tb):
49+
return
50+
51+
def infolist(self):
52+
return self.files
53+
54+
@classmethod
55+
def opener(cls):
56+
class mzip(cls):
57+
pass
58+
mzip.open = mock_open()
59+
return mzip
60+
61+
@contextmanager
62+
def yield_42():
63+
yield 42
3964

4065
class TestFileSource(AsyncTestCase):
4166

@@ -95,6 +120,13 @@ async def test_open_xz(self):
95120
await source.open()
96121
m_open.assert_called_once_with('testfile.xz', 'rt')
97122

123+
async def test_open_zip(self):
124+
source = FakeFileSource('testfile.zip')
125+
with patch('os.path.exists', return_value=True), \
126+
patch.object(source, 'zip_opener_helper', yield_42):
127+
await source.open()
128+
self.assertEqual(source.loaded_fd, 42)
129+
98130
async def test_open_no_file(self):
99131
source = FakeFileSource('testfile')
100132
with patch('os.path.isfile', return_value=False):
@@ -135,7 +167,13 @@ async def test_close_xz(self):
135167
with patch('lzma.open', m_open):
136168
await source.close()
137169
m_open.assert_called_once_with('testfile.xz', 'wt')
138-
170+
171+
async def test_close_zip(self):
172+
source = FakeFileSource('testfile.zip')
173+
with patch.object(source, 'zip_closer_helper', yield_42):
174+
await source.close()
175+
self.assertEqual(source.dumped_fd, 42)
176+
139177
async def test_close_readonly(self):
140178
source = FakeFileSource('testfile:ro')
141179
m_open = mock_open()

0 commit comments

Comments
 (0)