summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjvoisin2018-10-12 11:58:01 +0200
committerjvoisin2018-10-12 14:32:09 +0200
commit2ba38dd2a18ab57ed7aac7ccdd6a42ff5e4d4eb7 (patch)
tree7fe800485f6ea47b21f63195c6dfc2f32e675bfe
parentb832a5941458083dd6147efb652036552f95b786 (diff)
Bump mypy typing coverage
-rw-r--r--libmat2/__init__.py5
-rw-r--r--libmat2/abstract.py4
-rw-r--r--libmat2/archive.py3
-rw-r--r--libmat2/audio.py28
-rw-r--r--libmat2/harmless.py4
-rw-r--r--libmat2/images.py20
-rw-r--r--libmat2/office.py6
-rw-r--r--libmat2/pdf.py3
-rw-r--r--libmat2/torrent.py2
-rwxr-xr-xmat218
10 files changed, 52 insertions, 41 deletions
diff --git a/libmat2/__init__.py b/libmat2/__init__.py
index fbb61bc..f55a14c 100644
--- a/libmat2/__init__.py
+++ b/libmat2/__init__.py
@@ -8,6 +8,7 @@ from typing import Dict, Optional
8 8
9# make pyflakes happy 9# make pyflakes happy
10assert Dict 10assert Dict
11assert Optional
11 12
12# A set of extension that aren't supported, despite matching a supported mimetype 13# A set of extension that aren't supported, despite matching a supported mimetype
13UNSUPPORTED_EXTENSIONS = { 14UNSUPPORTED_EXTENSIONS = {
@@ -36,7 +37,7 @@ DEPENDENCIES = {
36 'mutagen': 'Mutagen', 37 'mutagen': 'Mutagen',
37 } 38 }
38 39
39def _get_exiftool_path() -> Optional[str]: # pragma: no cover 40def _get_exiftool_path() -> str: # pragma: no cover
40 exiftool_path = '/usr/bin/exiftool' 41 exiftool_path = '/usr/bin/exiftool'
41 if os.path.isfile(exiftool_path): 42 if os.path.isfile(exiftool_path):
42 if os.access(exiftool_path, os.X_OK): 43 if os.access(exiftool_path, os.X_OK):
@@ -48,7 +49,7 @@ def _get_exiftool_path() -> Optional[str]: # pragma: no cover
48 if os.access(exiftool_path, os.X_OK): 49 if os.access(exiftool_path, os.X_OK):
49 return exiftool_path 50 return exiftool_path
50 51
51 return None 52 raise ValueError
52 53
53def check_dependencies() -> dict: 54def check_dependencies() -> dict:
54 ret = collections.defaultdict(bool) # type: Dict[str, bool] 55 ret = collections.defaultdict(bool) # type: Dict[str, bool]
diff --git a/libmat2/abstract.py b/libmat2/abstract.py
index 5bcaa69..0084796 100644
--- a/libmat2/abstract.py
+++ b/libmat2/abstract.py
@@ -1,6 +1,6 @@
1import abc 1import abc
2import os 2import os
3from typing import Set, Dict 3from typing import Set, Dict, Union
4 4
5assert Set # make pyflakes happy 5assert Set # make pyflakes happy
6 6
@@ -22,7 +22,7 @@ class AbstractParser(abc.ABC):
22 self.lightweight_cleaning = False 22 self.lightweight_cleaning = False
23 23
24 @abc.abstractmethod 24 @abc.abstractmethod
25 def get_meta(self) -> Dict[str, str]: 25 def get_meta(self) -> Dict[str, Union[str, dict]]:
26 pass # pragma: no cover 26 pass # pragma: no cover
27 27
28 @abc.abstractmethod 28 @abc.abstractmethod
diff --git a/libmat2/archive.py b/libmat2/archive.py
index 016142d..f788ecc 100644
--- a/libmat2/archive.py
+++ b/libmat2/archive.py
@@ -4,13 +4,14 @@ import tempfile
4import os 4import os
5import logging 5import logging
6import shutil 6import shutil
7from typing import Dict, Set, Pattern 7from typing import Dict, Set, Pattern, Union
8 8
9from . import abstract, UnknownMemberPolicy, parser_factory 9from . import abstract, UnknownMemberPolicy, parser_factory
10 10
11# Make pyflakes happy 11# Make pyflakes happy
12assert Set 12assert Set
13assert Pattern 13assert Pattern
14assert Union
14 15
15 16
16class ArchiveBasedAbstractParser(abstract.AbstractParser): 17class ArchiveBasedAbstractParser(abstract.AbstractParser):
diff --git a/libmat2/audio.py b/libmat2/audio.py
index b67f766..bfe7f79 100644
--- a/libmat2/audio.py
+++ b/libmat2/audio.py
@@ -2,6 +2,7 @@ import mimetypes
2import os 2import os
3import shutil 3import shutil
4import tempfile 4import tempfile
5from typing import Dict, Union
5 6
6import mutagen 7import mutagen
7 8
@@ -16,13 +17,13 @@ class MutagenParser(abstract.AbstractParser):
16 except mutagen.MutagenError: 17 except mutagen.MutagenError:
17 raise ValueError 18 raise ValueError
18 19
19 def get_meta(self): 20 def get_meta(self) -> Dict[str, Union[str, dict]]:
20 f = mutagen.File(self.filename) 21 f = mutagen.File(self.filename)
21 if f.tags: 22 if f.tags:
22 return {k:', '.join(v) for k, v in f.tags.items()} 23 return {k:', '.join(v) for k, v in f.tags.items()}
23 return {} 24 return {}
24 25
25 def remove_all(self): 26 def remove_all(self) -> bool:
26 shutil.copy(self.filename, self.output_filename) 27 shutil.copy(self.filename, self.output_filename)
27 f = mutagen.File(self.output_filename) 28 f = mutagen.File(self.output_filename)
28 f.delete() 29 f.delete()
@@ -33,8 +34,8 @@ class MutagenParser(abstract.AbstractParser):
33class MP3Parser(MutagenParser): 34class MP3Parser(MutagenParser):
34 mimetypes = {'audio/mpeg', } 35 mimetypes = {'audio/mpeg', }
35 36
36 def get_meta(self): 37 def get_meta(self) -> Dict[str, Union[str, dict]]:
37 metadata = {} 38 metadata = {} # type: Dict[str, Union[str, dict]]
38 meta = mutagen.File(self.filename).tags 39 meta = mutagen.File(self.filename).tags
39 for key in meta: 40 for key in meta:
40 metadata[key.rstrip(' \t\r\n\0')] = ', '.join(map(str, meta[key].text)) 41 metadata[key.rstrip(' \t\r\n\0')] = ', '.join(map(str, meta[key].text))
@@ -48,7 +49,7 @@ class OGGParser(MutagenParser):
48class FLACParser(MutagenParser): 49class FLACParser(MutagenParser):
49 mimetypes = {'audio/flac', 'audio/x-flac'} 50 mimetypes = {'audio/flac', 'audio/x-flac'}
50 51
51 def remove_all(self): 52 def remove_all(self) -> bool:
52 shutil.copy(self.filename, self.output_filename) 53 shutil.copy(self.filename, self.output_filename)
53 f = mutagen.File(self.output_filename) 54 f = mutagen.File(self.output_filename)
54 f.clear_pictures() 55 f.clear_pictures()
@@ -56,16 +57,21 @@ class FLACParser(MutagenParser):
56 f.save(deleteid3=True) 57 f.save(deleteid3=True)
57 return True 58 return True
58 59
59 def get_meta(self): 60 def get_meta(self) -> Dict[str, Union[str, dict]]:
60 meta = super().get_meta() 61 meta = super().get_meta()
61 for num, picture in enumerate(mutagen.File(self.filename).pictures): 62 for num, picture in enumerate(mutagen.File(self.filename).pictures):
62 name = picture.desc if picture.desc else 'Cover %d' % num 63 name = picture.desc if picture.desc else 'Cover %d' % num
64 extension = mimetypes.guess_extension(picture.mime)
65 if extension is None: # pragma: no cover
66 meta[name] = 'harmful data'
67 continue
68
63 _, fname = tempfile.mkstemp() 69 _, fname = tempfile.mkstemp()
70 fname = fname + extension
64 with open(fname, 'wb') as f: 71 with open(fname, 'wb') as f:
65 f.write(picture.data) 72 f.write(picture.data)
66 extension = mimetypes.guess_extension(picture.mime) 73 p, _ = parser_factory.get_parser(fname) # type: ignore
67 shutil.move(fname, fname + extension) 74 # Mypy chokes on ternaries :/
68 p, _ = parser_factory.get_parser(fname+extension) 75 meta[name] = p.get_meta() if p else 'harmful data' # type: ignore
69 meta[name] = p.get_meta() if p else 'harmful data' 76 os.remove(fname)
70 os.remove(fname + extension)
71 return meta 77 return meta
diff --git a/libmat2/harmless.py b/libmat2/harmless.py
index f646099..fad0ef8 100644
--- a/libmat2/harmless.py
+++ b/libmat2/harmless.py
@@ -1,5 +1,5 @@
1import shutil 1import shutil
2from typing import Dict 2from typing import Dict, Union
3from . import abstract 3from . import abstract
4 4
5 5
@@ -7,7 +7,7 @@ class HarmlessParser(abstract.AbstractParser):
7 """ This is the parser for filetypes that can not contain metadata. """ 7 """ This is the parser for filetypes that can not contain metadata. """
8 mimetypes = {'text/plain', 'image/x-ms-bmp'} 8 mimetypes = {'text/plain', 'image/x-ms-bmp'}
9 9
10 def get_meta(self) -> Dict[str, str]: 10 def get_meta(self) -> Dict[str, Union[str, dict]]:
11 return dict() 11 return dict()
12 12
13 def remove_all(self) -> bool: 13 def remove_all(self) -> bool:
diff --git a/libmat2/images.py b/libmat2/images.py
index 8f7a98d..a29cbb7 100644
--- a/libmat2/images.py
+++ b/libmat2/images.py
@@ -5,7 +5,7 @@ import os
5import shutil 5import shutil
6import tempfile 6import tempfile
7import re 7import re
8from typing import Set 8from typing import Set, Dict, Union
9 9
10import cairo 10import cairo
11 11
@@ -25,7 +25,7 @@ class _ImageParser(abstract.AbstractParser):
25 meta_whitelist = set() # type: Set[str] 25 meta_whitelist = set() # type: Set[str]
26 26
27 @staticmethod 27 @staticmethod
28 def __handle_problematic_filename(filename: str, callback) -> str: 28 def __handle_problematic_filename(filename: str, callback) -> bytes:
29 """ This method takes a filename with a problematic name, 29 """ This method takes a filename with a problematic name,
30 and safely applies it a `callback`.""" 30 and safely applies it a `callback`."""
31 tmpdirname = tempfile.mkdtemp() 31 tmpdirname = tempfile.mkdtemp()
@@ -35,7 +35,7 @@ class _ImageParser(abstract.AbstractParser):
35 shutil.rmtree(tmpdirname) 35 shutil.rmtree(tmpdirname)
36 return out 36 return out
37 37
38 def get_meta(self): 38 def get_meta(self) -> Dict[str, Union[str, dict]]:
39 """ There is no way to escape the leading(s) dash(es) of the current 39 """ There is no way to escape the leading(s) dash(es) of the current
40 self.filename to prevent parameter injections, so we need to take care 40 self.filename to prevent parameter injections, so we need to take care
41 of this. 41 of this.
@@ -71,7 +71,7 @@ class PNGParser(_ImageParser):
71 except MemoryError: # pragma: no cover 71 except MemoryError: # pragma: no cover
72 raise ValueError 72 raise ValueError
73 73
74 def remove_all(self): 74 def remove_all(self) -> bool:
75 surface = cairo.ImageSurface.create_from_png(self.filename) 75 surface = cairo.ImageSurface.create_from_png(self.filename)
76 surface.write_to_png(self.output_filename) 76 surface.write_to_png(self.output_filename)
77 return True 77 return True
@@ -83,7 +83,12 @@ class GdkPixbufAbstractParser(_ImageParser):
83 """ 83 """
84 _type = '' 84 _type = ''
85 85
86 def remove_all(self): 86 def __init__(self, filename):
87 super().__init__(filename)
88 if imghdr.what(filename) != self._type: # better safe than sorry
89 raise ValueError
90
91 def remove_all(self) -> bool:
87 _, extension = os.path.splitext(self.filename) 92 _, extension = os.path.splitext(self.filename)
88 pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.filename) 93 pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.filename)
89 if extension.lower() == '.jpg': 94 if extension.lower() == '.jpg':
@@ -91,11 +96,6 @@ class GdkPixbufAbstractParser(_ImageParser):
91 pixbuf.savev(self.output_filename, extension[1:], [], []) 96 pixbuf.savev(self.output_filename, extension[1:], [], [])
92 return True 97 return True
93 98
94 def __init__(self, filename):
95 super().__init__(filename)
96 if imghdr.what(filename) != self._type: # better safe than sorry
97 raise ValueError
98
99 99
100class JPGParser(GdkPixbufAbstractParser): 100class JPGParser(GdkPixbufAbstractParser):
101 _type = 'jpeg' 101 _type = 'jpeg'
diff --git a/libmat2/office.py b/libmat2/office.py
index 32e7b75..c10664f 100644
--- a/libmat2/office.py
+++ b/libmat2/office.py
@@ -2,7 +2,7 @@ import logging
2import os 2import os
3import re 3import re
4import zipfile 4import zipfile
5from typing import Dict, Set, Pattern, Tuple 5from typing import Dict, Set, Pattern, Tuple, Union
6 6
7import xml.etree.ElementTree as ET # type: ignore 7import xml.etree.ElementTree as ET # type: ignore
8 8
@@ -296,7 +296,7 @@ class MSOfficeParser(ArchiveBasedAbstractParser):
296 296
297 return True 297 return True
298 298
299 def get_meta(self) -> Dict[str, str]: 299 def get_meta(self) -> Dict[str, Union[str, dict]]:
300 """ 300 """
301 Yes, I know that parsing xml with regexp ain't pretty, 301 Yes, I know that parsing xml with regexp ain't pretty,
302 be my guest and fix it if you want. 302 be my guest and fix it if you want.
@@ -381,7 +381,7 @@ class LibreOfficeParser(ArchiveBasedAbstractParser):
381 return False 381 return False
382 return True 382 return True
383 383
384 def get_meta(self) -> Dict[str, str]: 384 def get_meta(self) -> Dict[str, Union[str, dict]]:
385 """ 385 """
386 Yes, I know that parsing xml with regexp ain't pretty, 386 Yes, I know that parsing xml with regexp ain't pretty,
387 be my guest and fix it if you want. 387 be my guest and fix it if you want.
diff --git a/libmat2/pdf.py b/libmat2/pdf.py
index 140b4f4..17cd61e 100644
--- a/libmat2/pdf.py
+++ b/libmat2/pdf.py
@@ -7,6 +7,7 @@ import re
7import logging 7import logging
8import tempfile 8import tempfile
9import io 9import io
10from typing import Dict, Union
10from distutils.version import LooseVersion 11from distutils.version import LooseVersion
11 12
12import cairo 13import cairo
@@ -130,7 +131,7 @@ class PDFParser(abstract.AbstractParser):
130 metadata[key] = value 131 metadata[key] = value
131 return metadata 132 return metadata
132 133
133 def get_meta(self): 134 def get_meta(self) -> Dict[str, Union[str, dict]]:
134 """ Return a dict with all the meta of the file 135 """ Return a dict with all the meta of the file
135 """ 136 """
136 metadata = {} 137 metadata = {}
diff --git a/libmat2/torrent.py b/libmat2/torrent.py
index c56e971..4d6c1e0 100644
--- a/libmat2/torrent.py
+++ b/libmat2/torrent.py
@@ -14,7 +14,7 @@ class TorrentParser(abstract.AbstractParser):
14 if self.dict_repr is None: 14 if self.dict_repr is None:
15 raise ValueError 15 raise ValueError
16 16
17 def get_meta(self) -> Dict[str, str]: 17 def get_meta(self) -> Dict[str, Union[str, dict]]:
18 metadata = {} 18 metadata = {}
19 for key, value in self.dict_repr.items(): 19 for key, value in self.dict_repr.items():
20 if key not in self.whitelist: 20 if key not in self.whitelist:
diff --git a/mat2 b/mat2
index ba1f0ac..a7a3e73 100755
--- a/mat2
+++ b/mat2
@@ -1,7 +1,7 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2 2
3import os 3import os
4from typing import Tuple, Generator, List 4from typing import Tuple, Generator, List, Union
5import sys 5import sys
6import mimetypes 6import mimetypes
7import argparse 7import argparse
@@ -18,6 +18,7 @@ __version__ = '0.4.0'
18 18
19# Make pyflakes happy 19# Make pyflakes happy
20assert Tuple 20assert Tuple
21assert Union
21 22
22 23
23def __check_file(filename: str, mode: int=os.R_OK) -> bool: 24def __check_file(filename: str, mode: int=os.R_OK) -> bool:
@@ -98,12 +99,12 @@ def clean_meta(filename: str, is_lightweight: bool, policy: UnknownMemberPolicy)
98 return p.remove_all() 99 return p.remove_all()
99 100
100 101
101def show_parsers(): 102def show_parsers() -> bool:
102 print('[+] Supported formats:') 103 print('[+] Supported formats:')
103 formats = set() 104 formats = set() # Set[str]
104 for parser in parser_factory._get_parsers(): 105 for parser in parser_factory._get_parsers(): # type: ignore
105 for mtype in parser.mimetypes: 106 for mtype in parser.mimetypes:
106 extensions = set() 107 extensions = set() # Set[str]
107 for extension in mimetypes.guess_all_extensions(mtype): 108 for extension in mimetypes.guess_all_extensions(mtype):
108 if extension not in UNSUPPORTED_EXTENSIONS: 109 if extension not in UNSUPPORTED_EXTENSIONS:
109 extensions.add(extension) 110 extensions.add(extension)
@@ -113,6 +114,7 @@ def show_parsers():
113 continue 114 continue
114 formats.add(' - %s (%s)' % (mtype, ', '.join(extensions))) 115 formats.add(' - %s (%s)' % (mtype, ', '.join(extensions)))
115 print('\n'.join(sorted(formats))) 116 print('\n'.join(sorted(formats)))
117 return True
116 118
117 119
118def __get_files_recursively(files: List[str]) -> Generator[str, None, None]: 120def __get_files_recursively(files: List[str]) -> Generator[str, None, None]:
@@ -126,7 +128,7 @@ def __get_files_recursively(files: List[str]) -> Generator[str, None, None]:
126 elif __check_file(f): 128 elif __check_file(f):
127 yield f 129 yield f
128 130
129def main(): 131def main() -> int:
130 arg_parser = create_arg_parser() 132 arg_parser = create_arg_parser()
131 args = arg_parser.parse_args() 133 args = arg_parser.parse_args()
132 134
@@ -135,13 +137,13 @@ def main():
135 137
136 if not args.files: 138 if not args.files:
137 if args.list: 139 if args.list:
138 show_parsers() 140 return show_parsers()
139 elif args.check_dependencies: 141 elif args.check_dependencies:
140 print("Dependencies required for MAT2 %s:" % __version__) 142 print("Dependencies required for MAT2 %s:" % __version__)
141 for key, value in sorted(check_dependencies().items()): 143 for key, value in sorted(check_dependencies().items()):
142 print('- %s: %s' % (key, 'yes' if value else 'no')) 144 print('- %s: %s' % (key, 'yes' if value else 'no'))
143 else: 145 else:
144 return arg_parser.print_help() 146 arg_parser.print_help()
145 return 0 147 return 0
146 148
147 elif args.show: 149 elif args.show: