Compare commits

...
Author SHA1 Message Date
rembo10 c1edc9cde0 gh-workflow: rename, run on pull request, change python version 2022-01-23 14:34:10 +05:30
rembo10 ce98d0d6ca Ignore line length in flake8 2022-01-23 14:27:11 +05:30
rembo10 1bd7cc2ffd Whitespace fixes 2022-01-23 14:27:05 +05:30
rembo10 ad858576aa Add .flake8 configuration 2022-01-23 14:25:26 +05:30
rembo10 455b7d4940 Remove pylintrc 2022-01-23 14:25:26 +05:30
rembo10 cd14c3f4e2 travis -> github-actions 2022-01-23 14:25:26 +05:30
28 changed files with 181 additions and 431 deletions
+3
View File
@@ -0,0 +1,3 @@
[flake8]
exclude = .git,data,init-scripts,lib
ignore = E501
+29
View File
@@ -0,0 +1,29 @@
name: check
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 .
- name: Test with nosetests
run: |
nosetests
-25
View File
@@ -1,25 +0,0 @@
# Travis CI configuration file
# http://about.travis-ci.org/docs/
language: python
sudo: false
cache:
pip: true
directories:
- lib
python:
- "2.7"
install:
- pip install -r requirements-dev.txt
script:
- pep8 headphones
- pyflakes headphones
- nosetests
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == "2.7" ]]; then coveralls; fi
+6 -2
View File
@@ -474,8 +474,12 @@ class Api(object):
# Handle situations where the torrent url contains arguments that are # Handle situations where the torrent url contains arguments that are
# parsed # parsed
if kwargs: if kwargs:
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
url = urllib.parse.quote( url = urllib.parse.quote(
url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs) url, safe=":?/=&") + '&' + urllib.parse.urlencode(kwargs)
+3 -1
View File
@@ -18,7 +18,9 @@
####################################### #######################################
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
from .common import USER_AGENT from .common import USER_AGENT
+1
View File
@@ -31,6 +31,7 @@ class path(str):
def __repr__(self): def __repr__(self):
return 'headphones.config.path(%s)' % self return 'headphones.config.path(%s)' % self
_CONFIG_DEFINITIONS = { _CONFIG_DEFINITIONS = {
'ADD_ALBUM_ART': (int, 'General', 0), 'ADD_ALBUM_ART': (int, 'General', 0),
'ADVANCEDENCODER': (str, 'General', ''), 'ADVANCEDENCODER': (str, 'General', ''),
-1
View File
@@ -18,7 +18,6 @@
################################### ###################################
import time import time
import sqlite3 import sqlite3
-1
View File
@@ -35,7 +35,6 @@
# along with SickRage. If not, see <http://www.gnu.org/licenses/>. # along with SickRage. If not, see <http://www.gnu.org/licenses/>.
from headphones import logger from headphones import logger
import time import time
+2
View File
@@ -42,6 +42,7 @@ RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I) RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I) RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
def cmp(x, y): def cmp(x, y):
""" """
Replacement for built-in function cmp that was removed in Python 3 Replacement for built-in function cmp that was removed in Python 3
@@ -54,6 +55,7 @@ def cmp(x, y):
""" """
return (x > y) - (x < y) return (x > y) - (x < y)
def multikeysort(items, columns): def multikeysort(items, columns):
comparers = [ comparers = [
((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1))
-3
View File
@@ -201,8 +201,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`") logger.info(f"Found {dbtracks_count} new/modified tracks in `{dir}`")
logger.info("Matching tracks to the appropriate releases....") logger.info("Matching tracks to the appropriate releases....")
# Sort the track_list by most vague (e.g. no trackid or releaseid) # Sort the track_list by most vague (e.g. no trackid or releaseid)
# to most specific (both trackid & releaseid) # to most specific (both trackid & releaseid)
# When we insert into the database, the tracks with the most # When we insert into the database, the tracks with the most
@@ -210,7 +208,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None,
sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle']) sorted_dbtracks = helpers.multikeysort(dbtracks, ['ArtistName', 'AlbumTitle'])
# We'll use this to give a % completion, just because the # We'll use this to give a % completion, just because the
# track matching might take a while # track matching might take a while
tracks_completed = 0 tracks_completed = 0
+1
View File
@@ -38,6 +38,7 @@ class MetadataDict(dict):
lowercase) in member variable self._lower. If case-sensitive lookup lowercase) in member variable self._lower. If case-sensitive lookup
fails, another case-insensitive attempt is made. fails, another case-insensitive attempt is made.
""" """
def __setitem__(self, key, value): def __setitem__(self, key, value):
super(MetadataDict, self).__setitem__(key, value) super(MetadataDict, self).__setitem__(key, value)
self._lower.__setitem__(key.lower(), value) self._lower.__setitem__(key.lower(), value)
+6 -2
View File
@@ -1,5 +1,7 @@
from urllib.parse import urlencode, quote_plus from urllib.parse import urlencode, quote_plus
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import subprocess import subprocess
import json import json
from email.mime.text import MIMEText from email.mime.text import MIMEText
@@ -7,7 +9,9 @@ import smtplib
import email.utils import email.utils
from http.client import HTTPSConnection from http.client import HTTPSConnection
from urllib.parse import parse_qsl from urllib.parse import parse_qsl
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import requests as requests import requests as requests
import os.path import os.path
+3
View File
@@ -38,6 +38,7 @@ __author__ = "Andrzej Ciarkowski <andrzej.ciarkowski@gmail.com>"
class _PatternElement(object): class _PatternElement(object):
'''ABC for hierarchy of path name renderer pattern elements.''' '''ABC for hierarchy of path name renderer pattern elements.'''
def render(self, replacement): def render(self, replacement):
# type: (Mapping[str,str]) -> str # type: (Mapping[str,str]) -> str
'''Format this _PatternElement into string using provided substitution dictionary.''' '''Format this _PatternElement into string using provided substitution dictionary.'''
@@ -55,6 +56,7 @@ class _Generator(_PatternElement):
class _Replacement(_Generator): class _Replacement(_Generator):
'''Replacement variable, eg. $title.''' '''Replacement variable, eg. $title.'''
def __init__(self, pattern): def __init__(self, pattern):
# type: (str) # type: (str)
self._pattern = pattern self._pattern = pattern
@@ -81,6 +83,7 @@ class _Replacement(_Generator):
class _LiteralText(_PatternElement): class _LiteralText(_PatternElement):
'''Just a plain piece of text to be rendered "as is".''' '''Just a plain piece of text to be rendered "as is".'''
def __init__(self, text): def __init__(self, text):
# type: (str) # type: (str)
self._text = text self._text = text
+3 -1
View File
@@ -342,6 +342,7 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.warn(f"Could not identify {albumpath}. It may not be the intended album") logger.warn(f"Could not identify {albumpath}. It may not be the intended album")
markAsUnprocessed(albumid, albumpath, keep_original_folder) markAsUnprocessed(albumid, albumpath, keep_original_folder)
def markAsUnprocessed(albumid, albumpath, keep_original_folder=False): def markAsUnprocessed(albumid, albumpath, keep_original_folder=False):
myDB = db.DBConnection() myDB = db.DBConnection()
myDB.action( myDB.action(
@@ -595,7 +596,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_download(pushmessage) # twitter.notify_download(pushmessage)
if headphones.CONFIG.OSX_NOTIFY_ENABLED: if headphones.CONFIG.OSX_NOTIFY_ENABLED:
from headphones import cache from headphones import cache
@@ -1135,6 +1136,7 @@ def updateFilePermissions(albumpaths):
logger.error(f"Could not change permissions for `{full_path}`") logger.error(f"Could not change permissions for `{full_path}`")
continue continue
def renameUnprocessedFolder(path, tag): def renameUnprocessedFolder(path, tag):
""" """
Rename a unprocessed folder to a new unique name to indicate a certain Rename a unprocessed folder to a new unique name to indicate a certain
+6 -2
View File
@@ -13,8 +13,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.request, urllib.error, urllib.parse import urllib.parse
import urllib.error
import urllib.request
import urllib.error
import urllib.parse
import http.cookiejar import http.cookiejar
import json import json
import time import time
+3 -1
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import time import time
from urllib.parse import urlparse from urllib.parse import urlparse
import re import re
+4 -2
View File
@@ -19,7 +19,9 @@ from base64 import b16encode, b32decode
from hashlib import sha1 from hashlib import sha1
import string import string
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import datetime import datetime
import subprocess import subprocess
import unicodedata import unicodedata
@@ -1081,7 +1083,7 @@ def send_to_downloader(data, bestqual, album):
logger.info("Twitter notifications temporarily disabled") logger.info("Twitter notifications temporarily disabled")
#logger.info("Sending Twitter notification") #logger.info("Sending Twitter notification")
#twitter = notifiers.TwitterNotifier() #twitter = notifiers.TwitterNotifier()
#twitter.notify_snatch(name) # twitter.notify_snatch(name)
if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH: if headphones.CONFIG.NMA_ENABLED and headphones.CONFIG.NMA_ONSNATCH:
logger.info("Sending NMA notification") logger.info("Sending NMA notification")
nma = notifiers.NMA() nma = notifiers.NMA()
+6 -2
View File
@@ -13,11 +13,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>. # along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
from collections import namedtuple from collections import namedtuple
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import urllib.parse import urllib.parse
import http.cookiejar import http.cookiejar
+6 -2
View File
@@ -19,12 +19,16 @@ from operator import itemgetter
import threading import threading
import secrets import secrets
import random import random
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import json import json
import time import time
import sys import sys
from html import escape as html_escape from html import escape as html_escape
import urllib.request, urllib.error, urllib.parse import urllib.request
import urllib.error
import urllib.parse
import os import os
import re import re
-284
View File
@@ -1,284 +0,0 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook=sys.path.insert(0, 'lib/')
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#I0011 an inline option disables a pylint message or a messages category
#R0801 a set of similar lines has been detected among multiple file
#W0142 a function or method is called using *args or **kwargs to dispatch argument
# W1201(logging-not-lazy)
# C0330(bad-continuation)
# E1205(logging-too-many-args)
disable=I0011,R0801,W0142,C0103,C0111,C0301,C0302,C0304,C0321,C1001,E0101,E0203,E0602,E1101,E1123,R0201,R0401,R0911,R0912,R0914,R0915,R0923,W0102,W0109,W0120,W0141,W0201,W0212,W0231,W0232,W0233,W0301,W0311,W0401,W0403,W0404,W0511,W0601,W0602,W0603,W0611,W0612,W0613,W0621,W0622,W0633,W0702,W0703,W1401,W1201,C0330
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
#output-format=parseable
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the massage information. See doc for all details
#msg-template=
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=150
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent,objects
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=100
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
+4 -7
View File
@@ -1,8 +1,5 @@
coverage==4.0.3 coverage==6.2
coveralls==1.1 coveralls==3.3.1
mock==1.3.0 mock==4.0.3
nose==1.3.7 nose==1.3.7
pep8==1.7.0 flake8==4.0.1
pyflakes==1.1.0
pylint==1.3.1 # pylint 1.4 does not run under python 2.6
pyOpenSSL==0.15.1