|
5 | 5 | import io |
6 | 6 | import itertools |
7 | 7 | import os |
| 8 | +import pathlib |
8 | 9 | import posixpath |
9 | 10 | import stat |
10 | 11 | import struct |
@@ -485,6 +486,9 @@ def tearDown(self): |
485 | 486 |
|
486 | 487 | class StoredTestsWithSourceFile(AbstractTestsWithSourceFile, |
487 | 488 | unittest.TestCase): |
| 489 | + """ |
| 490 | + Test in which the files inside the archive are not compressed. |
| 491 | + """ |
488 | 492 | compression = zipfile.ZIP_STORED |
489 | 493 | test_low_compression = None |
490 | 494 |
|
@@ -676,6 +680,104 @@ def test_add_file_after_2107(self): |
676 | 680 | self.assertEqual(zinfo.date_time, (2107, 12, 31, 23, 59, 59)) |
677 | 681 |
|
678 | 682 |
|
| 683 | + class CustomZipInfo(zipfile.ZipInfo): |
| 684 | + """ |
| 685 | + Support for testing extending and subclassing ZipFile. |
| 686 | + """ |
| 687 | + |
| 688 | + class CustomZipExtFile(zipfile.ZipExtFile): |
| 689 | + """ |
| 690 | + Support for testing extending and subclassing ZipFile. |
| 691 | + """ |
| 692 | + |
| 693 | + def test_read_custom_zipinfo_and_zipextfile(self): |
| 694 | + """ |
| 695 | + A subclass of ZipFile can be implemented to read and handle the |
| 696 | + archive content using custom ZipInfo and ZipExtFile implementations. |
| 697 | + """ |
| 698 | + # Create the file using the default Zipfile. |
| 699 | + source = io.BytesIO() |
| 700 | + with zipfile.ZipFile(source, 'w', zipfile.ZIP_STORED) as zipfp: |
| 701 | + zipfp.writestr('test.txt', 'some-text-content') |
| 702 | + |
| 703 | + with zipfile.ZipFile( |
| 704 | + source, 'r', |
| 705 | + zipinfo_class=self.CustomZipInfo, |
| 706 | + zipextfile_class=self.CustomZipExtFile, |
| 707 | + ) as zipfp: |
| 708 | + # Archive content returns the custom ZipInfo |
| 709 | + members = zipfp.infolist() |
| 710 | + self.assertEqual(1, len(members)) |
| 711 | + self.assertIsInstance(members[0], self.CustomZipInfo) |
| 712 | + |
| 713 | + # Archive members can be opened using the custom ZipInfo |
| 714 | + target_member = members[0] |
| 715 | + with zipfp.open(target_member, mode='r') as memberfp: |
| 716 | + self.assertIsInstance(memberfp, self.CustomZipExtFile) |
| 717 | + |
| 718 | + def test_write_custom_zipinfo(self): |
| 719 | + """ |
| 720 | + A subclass of ZipFile can be implemented to write and handle the |
| 721 | + archive content using custom ZipInfo implementation. |
| 722 | + """ |
| 723 | + destination = io.BytesIO() |
| 724 | + with zipfile.ZipFile( |
| 725 | + destination, 'w', zipinfo_class=self.CustomZipInfo) as zipfp: |
| 726 | + # It can write using the specific custom classe. |
| 727 | + new_member = self.CustomZipInfo('new-member.txt') |
| 728 | + with zipfp.open(new_member, mode='w') as memberfp: |
| 729 | + self.assertIs(new_member, memberfp._zinfo) |
| 730 | + |
| 731 | + # When creating a new member using just the name, |
| 732 | + # the custom ZipInfo is used internally. |
| 733 | + with zipfp.open('other-member.txt', mode='w') as memberfp: |
| 734 | + self.assertIsInstance(memberfp._zinfo, self.CustomZipInfo) |
| 735 | + self.assertIsInstance( |
| 736 | + zipfp.NameToInfo['other-member.txt'], self.CustomZipInfo) |
| 737 | + |
| 738 | + # ZipFile.writestr can handle the custom class or just the |
| 739 | + # archive name as text. |
| 740 | + custom_member = self.CustomZipInfo('some-member.txt') |
| 741 | + zipfp.writestr(custom_member, b'some-new-content') |
| 742 | + zipfp.writestr('some-name.txt', b'other-content') |
| 743 | + self.assertIsInstance( |
| 744 | + zipfp.NameToInfo['some-name.txt'], self.CustomZipInfo) |
| 745 | + |
| 746 | + # ZipFile.mkdir can handle the custom class or just text. |
| 747 | + custom_dir = self.CustomZipInfo('some-directory/') |
| 748 | + custom_dir.CRC = 0 |
| 749 | + zipfp.mkdir(custom_dir) |
| 750 | + zipfp.mkdir('dir-as-text/') |
| 751 | + self.assertIsInstance( |
| 752 | + zipfp.NameToInfo['dir-as-text/'], self.CustomZipInfo) |
| 753 | + |
| 754 | + # When writing from an external file, the file is created using |
| 755 | + # the custom ZipInfo |
| 756 | + with temp_dir() as source_dir: |
| 757 | + source_file = pathlib.Path(source_dir).joinpath('source.txt') |
| 758 | + with open(source_file, 'wb') as fp: |
| 759 | + fp.write(b'some-content') |
| 760 | + zipfp.write(source_file, arcname='newly-file.txt') |
| 761 | + self.assertIsInstance( |
| 762 | + zipfp.NameToInfo['newly-file.txt'], self.CustomZipInfo) |
| 763 | + |
| 764 | + def test_extract_custom_zipinfo(self): |
| 765 | + """ |
| 766 | + A subclass of ZipFile can be implemented to extact the |
| 767 | + archive content using custom ZipInfo implementation. |
| 768 | + """ |
| 769 | + destination = io.BytesIO() |
| 770 | + with zipfile.ZipFile( |
| 771 | + destination, 'w', zipinfo_class=self.CustomZipInfo) as zipfp: |
| 772 | + zipfp.mkdir('dir-as-text/') |
| 773 | + dir_info = zipfp.NameToInfo['dir-as-text/'] |
| 774 | + self.assertIsInstance(dir_info, self.CustomZipInfo) |
| 775 | + |
| 776 | + with temp_dir() as extract_dir: |
| 777 | + zipfp.extract(dir_info, path=extract_dir) |
| 778 | + zipfp.extract('dir-as-text/', path=extract_dir) |
| 779 | + |
| 780 | + |
679 | 781 | @requires_zlib() |
680 | 782 | class DeflateTestsWithSourceFile(AbstractTestsWithSourceFile, |
681 | 783 | unittest.TestCase): |
|
0 commit comments