PEP8, MyPy, Pylint; Makefile glob bugfix

This commit is contained in:
DarkCat09 2023-02-08 15:00:20 +04:00
parent 426bd118aa
commit 74ccfec742
4 changed files with 231 additions and 34 deletions

1
.gitignore vendored
View file

@ -5,5 +5,6 @@ lyrics.txt
input input
__pycache__/ __pycache__/
.mypy_cache/
.vscode/ .vscode/
.idea/ .idea/

View file

@ -16,9 +16,9 @@ from typing import Optional, Any
import re import re
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup # type: ignore
from mutagen.id3 import ID3 from mutagen.id3 import ID3 # type: ignore
from mutagen.id3 import TPE1, TIT2, TALB from mutagen.id3 import TPE1, TIT2, TALB
from mutagen.id3 import TYER, TRCK from mutagen.id3 import TYER, TRCK
from mutagen.id3 import USLT, APIC from mutagen.id3 import USLT, APIC
@ -31,8 +31,6 @@ USERAGENT = (
LYRICS_ROW = '.main-page>.row>.col-xs-12' LYRICS_ROW = '.main-page>.row>.col-xs-12'
EDITMSG = 'edit'
safename_re = re.compile( safename_re = re.compile(
r'[^A-Za-z0-9А-ЯЁа-яё \'".,()\[\]&!#$@_~=*+-]' r'[^A-Za-z0-9А-ЯЁа-яё \'".,()\[\]&!#$@_~=*+-]'
) )
@ -53,6 +51,18 @@ class ParseResult(TypedDict):
cover_mime: Optional[str] cover_mime: Optional[str]
class ParseError(Exception):
EDIT = 'edit'
def __init__(self, parsing_obj: str) -> None:
super().__init__(
f'Unable to parse {parsing_obj}'
)
self.parsing_obj = parsing_obj
parsed = ParseResult( parsed = ParseResult(
title='', artist='', title='', artist='',
album='', year=0, album='', year=0,
@ -63,16 +73,6 @@ parsed = ParseResult(
) )
class ParseError(Exception):
def __init__(self, parsing_obj: str) -> None:
super().__init__(
f'Unable to parse {parsing_obj}'
)
self.parsing_obj = parsing_obj
def main() -> None: def main() -> None:
global parsed global parsed
@ -108,9 +108,11 @@ def main() -> None:
print(err) print(err)
# pylint: disable=no-member
if isinstance(err, ParseError) \ if isinstance(err, ParseError) \
and err.parsing_obj == EDITMSG: and err.parsing_obj == ParseError.EDIT:
pass pass
# pylint: enable=no-member
else: else:
print( print(
@ -121,10 +123,10 @@ def main() -> None:
manual_info_input(False) manual_info_input(False)
#print(parsed) tagmp3(file, copy)
tagmp3(file, parsed, copy)
# pylint: disable=redefined-builtin
def input(msg: str = '', def_: Any = '') -> str: def input(msg: str = '', def_: Any = '') -> str:
subprocess.call( subprocess.call(
@ -143,6 +145,7 @@ def input(msg: str = '', def_: Any = '') -> str:
.removesuffix('\r') .removesuffix('\r')
except Exception: except Exception:
return def_ return def_
# pylint: enable=redefined-builtin
def input_num(msg: str, def_: int = 0) -> int: def input_num(msg: str, def_: int = 0) -> int:
@ -189,10 +192,10 @@ def conv_title(file: str) -> str:
def search_azurl(title: str) -> str: def search_azurl(title: str) -> str:
print('Searching...') print('Searching...')
page = session.get( page = session.get(
'https://searx.dc09.ru/search', 'https://searx.dc09.ru/search',
params={ params={ # type: ignore
'q': f'{title} site:azlyrics.com', 'q': f'{title} site:azlyrics.com',
'language': 'ru-RU', 'language': 'ru-RU',
'safesearch': 0, 'safesearch': 0,
@ -207,7 +210,7 @@ def search_azurl(title: str) -> str:
if link is None: if link is None:
raise ParseError('song URL') raise ParseError('song URL')
return str(link.get('href')) return str(link.get('href'))
@ -219,7 +222,7 @@ def parse_azlyrics(link: str) -> None:
page = session.get(link) page = session.get(link)
soup = BeautifulSoup(page.text, 'html.parser') soup = BeautifulSoup(page.text, 'html.parser')
lyrics = soup.select_one( lyrics = soup.select_one(
f'{LYRICS_ROW}>div' f'{LYRICS_ROW}>div'
':not(.div-share)' ':not(.div-share)'
@ -262,7 +265,7 @@ def parse_azlyrics(link: str) -> None:
) )
if album_re is None: if album_re is None:
raise ParseError('album name') raise ParseError('album name')
parsed['album'] = album_re[1] parsed['album'] = album_re[1]
parsed['year'] = int(album_re[2]) parsed['year'] = int(album_re[2])
@ -279,7 +282,7 @@ def parse_azlyrics(link: str) -> None:
parsed['cover_mime'] = req.headers.get( parsed['cover_mime'] = req.headers.get(
'Content-Type', 'image/jpeg' 'Content-Type', 'image/jpeg'
) )
tracklist_elem = soup.select_one('.songlist-panel') tracklist_elem = soup.select_one('.songlist-panel')
if tracklist_elem is not None: if tracklist_elem is not None:
@ -305,7 +308,7 @@ def parse_azlyrics(link: str) -> None:
if current_url[0] in track_href: if current_url[0] in track_href:
parsed['track_no'] = (i + 1) parsed['track_no'] = (i + 1)
break break
print('Succesfully parsed') print('Succesfully parsed')
print('Title:', parsed['title']) print('Title:', parsed['title'])
print('Artist:', parsed['artist']) print('Artist:', parsed['artist'])
@ -314,9 +317,9 @@ def parse_azlyrics(link: str) -> None:
print('Correct something?') print('Correct something?')
if input('[y/N] ').lower == 'y': if input('[y/N] ').lower == 'y':
raise ParseError('edit') raise ParseError(ParseError.EDIT)
else:
print() print()
def manual_info_input(overwrite_lyrics: bool = True) -> None: def manual_info_input(overwrite_lyrics: bool = True) -> None:
@ -378,18 +381,19 @@ def manual_info_input(overwrite_lyrics: bool = True) -> None:
) )
except Exception as err: except Exception as err:
logging.exception(err) logging.exception(err)
print() print()
def tagmp3( def tagmp3(
file: str, file: str,
parsed: ParseResult,
copy: bool) -> None: copy: bool) -> None:
global parsed
oldpath = Path(file) oldpath = Path(file)
newpath = oldpath newpath = oldpath
if copy: if copy:
newdir = ( newdir = (
@ -414,7 +418,7 @@ def tagmp3(
cover = newdir / f'cover{ext}' cover = newdir / f'cover{ext}'
with cover.open('wb') as f: with cover.open('wb') as f:
f.write(parsed['cover']) f.write(parsed['cover'])
id3 = ID3(str(newpath)) id3 = ID3(str(newpath))
id3['TPE1'] = TPE1(text=parsed['artist']) id3['TPE1'] = TPE1(text=parsed['artist'])
id3['TIT2'] = TIT2(text=parsed['title']) id3['TIT2'] = TIT2(text=parsed['title'])

View file

@ -1,5 +1,6 @@
clean: clean:
rm -rf __pycache__/ rm -rf __pycache__/
rm -rf .mypy_cache/
rm -f lyrics.txt input rm -f lyrics.txt input
run: run:
@ -15,8 +16,8 @@ tags:
./id3tag.sh ./id3tag.sh
pyformat: pyformat:
python3 -m autopep8 --in-place ./*.py python3 -m autopep8 --in-place .*.py
pycheck: pycheck:
python3 -m mypy ./*.py python3 -m mypy .*.py
python3 -m pylint -j $(nproc) ./*.py python3 -m pylint -j4 .*.py

191
pylintrc Normal file
View file

@ -0,0 +1,191 @@
[MAIN]
analyse-fallback-blocks=no
extension-pkg-allow-list=
extension-pkg-whitelist=
fail-on=
fail-under=10
ignore=CVS
ignore-paths=
ignore-patterns=^\.#
ignored-modules=
jobs=4
limit-inference-results=100
load-plugins=
persistent=yes
py-version=3.10
recursive=no
suggestion-mode=yes
unsafe-load-any-extension=no
[REPORTS]
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
msg-template=
reports=no
score=yes
[MESSAGES CONTROL]
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
too-many-branches,
too-many-statements,
too-many-locals,
broad-except,
global-variable-not-assigned
enable=c-extension-no-member
[SIMILARITIES]
ignore-comments=yes
ignore-docstrings=yes
ignore-imports=yes
ignore-signatures=yes
min-similarity-lines=4
[MISCELLANEOUS]
notes=FIXME,
XXX,
TODO
notes-rgx=
[DESIGN]
exclude-too-few-public-methods=
ignored-parents=
max-args=5
max-attributes=7
max-bool-expr=5
max-branches=12
max-locals=15
max-parents=7
max-public-methods=20
max-returns=6
max-statements=50
min-public-methods=2
[STRING]
check-quote-consistency=no
check-str-concat-over-line-jumps=no
[CLASSES]
check-protected-access-in-special-methods=no
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
valid-classmethod-first-arg=cls
valid-metaclass-classmethod-first-arg=cls
[FORMAT]
expected-line-ending-format=
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
indent-after-paren=4
indent-string=' '
max-line-length=100
max-module-lines=1000
single-line-class-stmt=no
single-line-if-stmt=no
[IMPORTS]
allow-any-import-level=
allow-wildcard-with-all=no
deprecated-modules=
ext-import-graph=
import-graph=
int-import-graph=
known-standard-library=
known-third-party=enchant
preferred-modules=
[VARIABLES]
additional-builtins=
allow-global-unused-variables=yes
allowed-redefined-builtins=
callbacks=cb_,
_cb
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
ignored-argument-names=_.*|^ignored_|^unused_
init-import=no
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[LOGGING]
logging-format-style=old
logging-modules=logging
[EXCEPTIONS]
overgeneral-exceptions=BaseException,
Exception
[BASIC]
argument-naming-style=snake_case
attr-naming-style=snake_case
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
bad-names-rgxs=
class-attribute-naming-style=any
class-const-naming-style=UPPER_CASE
class-naming-style=PascalCase
const-naming-style=any
docstring-min-length=-1
function-naming-style=snake_case
good-names=i,
j,
k,
f,
ex,
Run,
_
good-names-rgxs=
include-naming-hint=no
inlinevar-naming-style=any
method-naming-style=snake_case
module-naming-style=snake_case
name-group=
no-docstring-rgx=^_
property-classes=abc.abstractproperty
variable-naming-style=snake_case
[SPELLING]
max-spelling-suggestions=4
spelling-dict=
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
spelling-ignore-words=
spelling-private-dict-file=
spelling-store-unknown-words=no
[TYPECHECK]
contextmanager-decorators=contextlib.contextmanager
generated-members=
ignore-none=yes
ignore-on-opaque-inference=yes
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
missing-member-hint=yes
missing-member-hint-distance=1
missing-member-max-choices=1
mixin-class-rgx=.*[Mm]ixin
signature-mutators=
[REFACTORING]
max-nested-blocks=5
never-returning-functions=sys.exit,argparse.parse_error