diff options
| author | jvoisin | 2018-10-12 11:58:01 +0200 |
|---|---|---|
| committer | jvoisin | 2018-10-12 14:32:09 +0200 |
| commit | 2ba38dd2a18ab57ed7aac7ccdd6a42ff5e4d4eb7 (patch) | |
| tree | 7fe800485f6ea47b21f63195c6dfc2f32e675bfe | |
| parent | b832a5941458083dd6147efb652036552f95b786 (diff) | |
Bump mypy typing coverage
| -rw-r--r-- | libmat2/__init__.py | 5 | ||||
| -rw-r--r-- | libmat2/abstract.py | 4 | ||||
| -rw-r--r-- | libmat2/archive.py | 3 | ||||
| -rw-r--r-- | libmat2/audio.py | 28 | ||||
| -rw-r--r-- | libmat2/harmless.py | 4 | ||||
| -rw-r--r-- | libmat2/images.py | 20 | ||||
| -rw-r--r-- | libmat2/office.py | 6 | ||||
| -rw-r--r-- | libmat2/pdf.py | 3 | ||||
| -rw-r--r-- | libmat2/torrent.py | 2 | ||||
| -rwxr-xr-x | mat2 | 18 |
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 |
| 10 | assert Dict | 10 | assert Dict |
| 11 | assert 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 |
| 13 | UNSUPPORTED_EXTENSIONS = { | 14 | UNSUPPORTED_EXTENSIONS = { |
| @@ -36,7 +37,7 @@ DEPENDENCIES = { | |||
| 36 | 'mutagen': 'Mutagen', | 37 | 'mutagen': 'Mutagen', |
| 37 | } | 38 | } |
| 38 | 39 | ||
| 39 | def _get_exiftool_path() -> Optional[str]: # pragma: no cover | 40 | def _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 | ||
| 53 | def check_dependencies() -> dict: | 54 | def 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 @@ | |||
| 1 | import abc | 1 | import abc |
| 2 | import os | 2 | import os |
| 3 | from typing import Set, Dict | 3 | from typing import Set, Dict, Union |
| 4 | 4 | ||
| 5 | assert Set # make pyflakes happy | 5 | assert 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 | |||
| 4 | import os | 4 | import os |
| 5 | import logging | 5 | import logging |
| 6 | import shutil | 6 | import shutil |
| 7 | from typing import Dict, Set, Pattern | 7 | from typing import Dict, Set, Pattern, Union |
| 8 | 8 | ||
| 9 | from . import abstract, UnknownMemberPolicy, parser_factory | 9 | from . import abstract, UnknownMemberPolicy, parser_factory |
| 10 | 10 | ||
| 11 | # Make pyflakes happy | 11 | # Make pyflakes happy |
| 12 | assert Set | 12 | assert Set |
| 13 | assert Pattern | 13 | assert Pattern |
| 14 | assert Union | ||
| 14 | 15 | ||
| 15 | 16 | ||
| 16 | class ArchiveBasedAbstractParser(abstract.AbstractParser): | 17 | class 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 | |||
| 2 | import os | 2 | import os |
| 3 | import shutil | 3 | import shutil |
| 4 | import tempfile | 4 | import tempfile |
| 5 | from typing import Dict, Union | ||
| 5 | 6 | ||
| 6 | import mutagen | 7 | import 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): | |||
| 33 | class MP3Parser(MutagenParser): | 34 | class 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): | |||
| 48 | class FLACParser(MutagenParser): | 49 | class 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 @@ | |||
| 1 | import shutil | 1 | import shutil |
| 2 | from typing import Dict | 2 | from typing import Dict, Union |
| 3 | from . import abstract | 3 | from . 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 | |||
| 5 | import shutil | 5 | import shutil |
| 6 | import tempfile | 6 | import tempfile |
| 7 | import re | 7 | import re |
| 8 | from typing import Set | 8 | from typing import Set, Dict, Union |
| 9 | 9 | ||
| 10 | import cairo | 10 | import 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 | ||
| 100 | class JPGParser(GdkPixbufAbstractParser): | 100 | class 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 | |||
| 2 | import os | 2 | import os |
| 3 | import re | 3 | import re |
| 4 | import zipfile | 4 | import zipfile |
| 5 | from typing import Dict, Set, Pattern, Tuple | 5 | from typing import Dict, Set, Pattern, Tuple, Union |
| 6 | 6 | ||
| 7 | import xml.etree.ElementTree as ET # type: ignore | 7 | import 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 | |||
| 7 | import logging | 7 | import logging |
| 8 | import tempfile | 8 | import tempfile |
| 9 | import io | 9 | import io |
| 10 | from typing import Dict, Union | ||
| 10 | from distutils.version import LooseVersion | 11 | from distutils.version import LooseVersion |
| 11 | 12 | ||
| 12 | import cairo | 13 | import 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: |
| @@ -1,7 +1,7 @@ | |||
| 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 |
| 2 | 2 | ||
| 3 | import os | 3 | import os |
| 4 | from typing import Tuple, Generator, List | 4 | from typing import Tuple, Generator, List, Union |
| 5 | import sys | 5 | import sys |
| 6 | import mimetypes | 6 | import mimetypes |
| 7 | import argparse | 7 | import argparse |
| @@ -18,6 +18,7 @@ __version__ = '0.4.0' | |||
| 18 | 18 | ||
| 19 | # Make pyflakes happy | 19 | # Make pyflakes happy |
| 20 | assert Tuple | 20 | assert Tuple |
| 21 | assert Union | ||
| 21 | 22 | ||
| 22 | 23 | ||
| 23 | def __check_file(filename: str, mode: int=os.R_OK) -> bool: | 24 | def __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 | ||
| 101 | def show_parsers(): | 102 | def 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 | ||
| 118 | def __get_files_recursively(files: List[str]) -> Generator[str, None, None]: | 120 | def __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 | ||
| 129 | def main(): | 131 | def 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: |
